First working JCR file system
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 5 Nov 2016 20:11:04 +0000 (20:11 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 5 Nov 2016 20:11:04 +0000 (20:11 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@9310 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

20 files changed:
org.argeo.jcr/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java [new file with mode: 0644]
org.argeo.jcr/ext/test/org/argeo/server/jcr/JcrResourceAdapterTest.java
org.argeo.jcr/repository.xml [new file with mode: 0644]
org.argeo.jcr/repository/repository/meta/rootUUID [new file with mode: 0644]
org.argeo.jcr/repository/repository/namespaces/ns_idx.properties [new file with mode: 0644]
org.argeo.jcr/repository/repository/namespaces/ns_reg.properties [new file with mode: 0644]
org.argeo.jcr/repository/workspaces/default/workspace.xml [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/JcrResourceAdapter.java
org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java [new file with mode: 0644]
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/NodeDirectoryStream.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/fs/SessionFsProvider.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/fs/WorkSpaceFileStore.java [new file with mode: 0644]

diff --git a/org.argeo.jcr/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java b/org.argeo.jcr/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java
new file mode 100644 (file)
index 0000000..1cf7ef7
--- /dev/null
@@ -0,0 +1,97 @@
+package org.argeo.jcr.fs;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.jcr.Property;
+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.argeo.jackrabbit.fs.JackrabbitMemoryFsProvider;
+
+import junit.framework.TestCase;
+
+public class JcrFileSystemTest extends TestCase {
+       private final static Log log = LogFactory.getLog(JcrFileSystemTest.class);
+
+       public void testSimple() throws Exception {
+               FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
+
+               // Simple file
+               Path testPath = fsProvider.getPath(new URI("jcr+memory:/test.txt"));
+               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);
+               String txt = "TEST\nTEST2\n";
+               byte[] arr = txt.getBytes();
+               Files.write(testPath, arr);
+               log.debug("Wrote " + testPath);
+               byte[] read = Files.readAllBytes(testPath);
+               assertTrue(Arrays.equals(arr, read));
+               assertEquals(txt, new String(read));
+               log.debug("Read " + testPath);
+               Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
+               log.debug("Got root " + rootPath);
+               Path testDir = rootPath.resolve("testDir");
+               log.debug("Resolved " + testDir);
+               // Copy
+               Files.createDirectory(testDir);
+               log.debug("Created directory " + testDir);
+               Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
+               log.debug("Created sub directories " + subsubdir);
+               Path copiedFile = testDir.resolve("copiedFile.txt");
+               log.debug("Resolved " + copiedFile);
+               try (OutputStream out = Files.newOutputStream(copiedFile); InputStream in = Files.newInputStream(testPath)) {
+                       IOUtils.copy(in, out);
+               }
+               log.debug("Copied " + testPath + " to " + copiedFile);
+               Files.delete(testPath);
+               log.debug("Deleted " + testPath);
+               byte[] copiedRead = Files.readAllBytes(copiedFile);
+               assertTrue(Arrays.equals(copiedRead, read));
+               log.debug("Read " + copiedFile);
+               // Browse directories
+               DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+               int fileCount = 0;
+               Path listedFile = null;
+               for (Path file : files) {
+                       fileCount++;
+                       if (!Files.isDirectory(file))
+                               listedFile = file;
+               }
+               assertEquals(2, fileCount);
+               assertEquals(copiedFile, listedFile);
+               assertEquals(copiedFile.toString(), listedFile.toString());
+               log.debug("Listed " + testDir);
+               // Generic attributes
+               Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
+               assertEquals(5, attrs.size());
+               log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+               // Direct node access
+               NodeFileAttributes nfa = Files.readAttributes(copiedFile, NodeFileAttributes.class);
+               nfa.getNode().addMixin(NodeType.MIX_LANGUAGE);
+               nfa.getNode().getSession().save();
+               log.debug("Add mix:language");
+               Files.setAttribute(copiedFile, Property.JCR_LANGUAGE, "fr");
+               log.debug("Set language");
+               attrs = Files.readAttributes(copiedFile, "*");
+               assertEquals(6, attrs.size());
+               log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+       }
+}
index 3ce499af8060a3470ff632b047b1d78cf3746567..11dc4fab05680a0a6815c6793cf3fcdd6ed93e7c 100644 (file)
@@ -28,6 +28,7 @@ import org.argeo.jcr.JcrResourceAdapter;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.core.io.Resource;
 
+@Deprecated
 public class JcrResourceAdapterTest extends AbstractJackrabbitTestCase {
        private static SimpleDateFormat sdf = new SimpleDateFormat(
                        "yyyyMMdd:hhmmss.SSS");
diff --git a/org.argeo.jcr/repository.xml b/org.argeo.jcr/repository.xml
new file mode 100644 (file)
index 0000000..745079e
--- /dev/null
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>\r
+<!--\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+-->\r
+\r
+<!DOCTYPE Repository\r
+          PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.0//EN"\r
+          "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">\r
+\r
+<!-- Example Repository Configuration File\r
+     Used by\r
+     - org.apache.jackrabbit.core.config.RepositoryConfigTest.java\r
+     -\r
+-->\r
+<Repository>\r
+    <!--\r
+        virtual file system where the repository stores global state\r
+        (e.g. registered namespaces, custom node types, etc.)\r
+    -->\r
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">\r
+        <param name="path" value="${rep.home}/repository"/>\r
+    </FileSystem>\r
+\r
+    <!--\r
+        data store configuration\r
+    -->\r
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>\r
+\r
+    <!--\r
+        security configuration\r
+    -->\r
+    <Security appName="Jackrabbit">\r
+        <!--\r
+            security manager:\r
+            class: FQN of class implementing the JackrabbitSecurityManager interface\r
+        -->\r
+        <SecurityManager class="org.apache.jackrabbit.core.DefaultSecurityManager" workspaceName="security">\r
+            <!--\r
+            workspace access:\r
+            class: FQN of class implementing the WorkspaceAccessManager interface\r
+            -->\r
+            <!-- <WorkspaceAccessManager class="..."/> -->\r
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->\r
+        </SecurityManager>\r
+\r
+        <!--\r
+            access manager:\r
+            class: FQN of class implementing the AccessManager interface\r
+        -->\r
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">\r
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->\r
+        </AccessManager>\r
+\r
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">\r
+           <!-- \r
+              anonymous user name ('anonymous' is the default value)\r
+            -->\r
+           <param name="anonymousId" value="anonymous"/>\r
+           <!--\r
+              administrator user id (default value if param is missing is 'admin')\r
+            -->\r
+           <param name="adminId" value="admin"/>\r
+        </LoginModule>\r
+    </Security>\r
+\r
+    <!--\r
+        location of workspaces root directory and name of default workspace\r
+    -->\r
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>\r
+    <!--\r
+        workspace configuration template:\r
+        used to create the initial workspace if there's no workspace yet\r
+    -->\r
+    <Workspace name="${wsp.name}">\r
+        <!--\r
+            virtual file system of the workspace:\r
+            class: FQN of class implementing the FileSystem interface\r
+        -->\r
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">\r
+            <param name="path" value="${wsp.home}"/>\r
+        </FileSystem>\r
+        <!--\r
+            persistence manager of the workspace:\r
+            class: FQN of class implementing the PersistenceManager interface\r
+        -->\r
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">\r
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>\r
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>\r
+        </PersistenceManager>\r
+        <!--\r
+            Search index and the file system it uses.\r
+            class: FQN of class implementing the QueryHandler interface\r
+        -->\r
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">\r
+            <param name="path" value="${wsp.home}/index"/>\r
+            <param name="supportHighlighting" value="true"/>\r
+        </SearchIndex>\r
+    </Workspace>\r
+\r
+    <!--\r
+        Configures the versioning\r
+    -->\r
+    <Versioning rootPath="${rep.home}/version">\r
+        <!--\r
+            Configures the filesystem to use for versioning for the respective\r
+            persistence manager\r
+        -->\r
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">\r
+            <param name="path" value="${rep.home}/version" />\r
+        </FileSystem>\r
+\r
+        <!--\r
+            Configures the persistence manager to be used for persisting version state.\r
+            Please note that the current versioning implementation is based on\r
+            a 'normal' persistence manager, but this could change in future\r
+            implementations.\r
+        -->\r
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">\r
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>\r
+          <param name="schemaObjectPrefix" value="version_"/>\r
+        </PersistenceManager>\r
+    </Versioning>\r
+\r
+    <!--\r
+        Search index for content that is shared repository wide\r
+        (/jcr:system tree, contains mainly versions)\r
+    -->\r
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">\r
+        <param name="path" value="${rep.home}/repository/index"/>\r
+        <param name="supportHighlighting" value="true"/>\r
+    </SearchIndex>\r
+\r
+    <!--\r
+        Run with a cluster journal\r
+    -->\r
+    <Cluster id="node1">\r
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>\r
+    </Cluster>\r
+</Repository>\r
diff --git a/org.argeo.jcr/repository/repository/meta/rootUUID b/org.argeo.jcr/repository/repository/meta/rootUUID
new file mode 100644 (file)
index 0000000..df09293
--- /dev/null
@@ -0,0 +1 @@
+cafebabe-cafe-babe-cafe-babecafebabe
\ No newline at end of file
diff --git a/org.argeo.jcr/repository/repository/namespaces/ns_idx.properties b/org.argeo.jcr/repository/repository/namespaces/ns_idx.properties
new file mode 100644 (file)
index 0000000..7e757f0
--- /dev/null
@@ -0,0 +1,8 @@
+#Fri Oct 28 20:14:30 CEST 2016
+http\://www.jcp.org/jcr/1.0=1570322
+internal=16762557
+http\://www.jcp.org/jcr/sv/1.0=16463688
+http\://www.jcp.org/jcr/mix/1.0=14361695
+http\://www.jcp.org/jcr/nt/1.0=5688619
+.empty.key=0
+http\://www.w3.org/XML/1998/namespace=6829023
diff --git a/org.argeo.jcr/repository/repository/namespaces/ns_reg.properties b/org.argeo.jcr/repository/repository/namespaces/ns_reg.properties
new file mode 100644 (file)
index 0000000..f40bf21
--- /dev/null
@@ -0,0 +1,8 @@
+#Fri Oct 28 20:14:30 CEST 2016
+jcr=http\://www.jcp.org/jcr/1.0
+sv=http\://www.jcp.org/jcr/sv/1.0
+xml=http\://www.w3.org/XML/1998/namespace
+nt=http\://www.jcp.org/jcr/nt/1.0
+mix=http\://www.jcp.org/jcr/mix/1.0
+rep=internal
+.empty.key=
diff --git a/org.argeo.jcr/repository/workspaces/default/workspace.xml b/org.argeo.jcr/repository/workspaces/default/workspace.xml
new file mode 100644 (file)
index 0000000..a32f9c7
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?><Workspace name="default">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+            <param name="supportHighlighting" value="true"/>
+        </SearchIndex>
+    </Workspace>
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/org.argeo.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java
new file mode 100644 (file)
index 0000000..a2eb983
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.jackrabbit.fs;
+
+import org.argeo.jcr.fs.JcrFileSystemProvider;
+
+public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider {
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/org.argeo.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java
new file mode 100644 (file)
index 0000000..0ed8268
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.jackrabbit.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.jcr.fs.JcrFileSystem;
+import org.argeo.jcr.fs.JcrFsException;
+
+public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
+       private RepositoryImpl repository;
+       private JcrFileSystem fileSystem;
+
+       @Override
+       public String getScheme() {
+               return "jcr+memory";
+       }
+
+       @Override
+       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+               try {
+                       Path tempDir = Files.createTempDirectory("fs-memory");
+                       URL confUrl = getClass().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);
+                       return fileSystem;
+               } catch (Exception e) {
+                       throw new JcrFsException("Cannot login to repository", e);
+               }
+       }
+
+       @Override
+       public FileSystem getFileSystem(URI uri) {
+               return fileSystem;
+       }
+
+       @Override
+       public Path getPath(URI uri) {
+               String path = uri.getPath();
+               if(fileSystem==null)
+                       try {
+                               newFileSystem(uri, new HashMap<String, Object>());
+                       } catch (IOException e) {
+                               throw new JcrFsException("Could not autocreate file system",e);
+                       }
+               return fileSystem.getPath(path);
+       }
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml b/org.argeo.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml
new file mode 100644 (file)
index 0000000..c7bab0d
--- /dev/null
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="main" configRootPath="/workspaces" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${wsp.home}/index" />
+                       <param name="directoryManagerClass"
+                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/index" />
+               <param name="directoryManagerClass"
+                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule" />
+               <!-- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager" -->
+               <!-- workspaceName="security" /> -->
+               <!-- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" 
+                       /> -->
+       </Security>
+</Repository>
\ No newline at end of file
index 1ccce4f769972fc2c9296127bf8db614520f6484..be7bf4959b5cb21bee1e84e2c27f460352d07b53 100644 (file)
@@ -37,6 +37,7 @@ import org.apache.commons.logging.LogFactory;
  * Bridge Spring resources and JCR folder / files semantics (nt:folder /
  * nt:file), supporting versioning as well.
  */
+@Deprecated
 public class JcrResourceAdapter {
        private final static Log log = LogFactory.getLog(JcrResourceAdapter.class);
 
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java
new file mode 100644 (file)
index 0000000..ff4b4a1
--- /dev/null
@@ -0,0 +1,197 @@
+package org.argeo.jcr.fs;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+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.argeo.jcr.JcrUtils;
+
+public class BinaryChannel implements SeekableByteChannel {
+       private final Node file;
+       private Binary binary;
+       private boolean open = true;
+
+       private long position = 0;
+
+       // private ByteBuffer toWrite;
+       private FileChannel fc = null;
+
+       public BinaryChannel(Node file) throws RepositoryException, IOException {
+               this.file = file;
+               // int capacity = 1024 * 1024;
+               // this.toWrite = ByteBuffer.allocate(capacity);
+               if (file.isNodeType(NodeType.NT_FILE)) {
+                       if (file.hasNode(Property.JCR_CONTENT)) {
+                               Node data = file.getNode(Property.JCR_CONTENT);
+                               this.binary = data.getProperty(Property.JCR_DATA).getBinary();
+                       } else {
+                               Node data = file.addNode(Property.JCR_CONTENT, NodeType.NT_RESOURCE);
+                               try (InputStream in = new ByteArrayInputStream(new byte[0])) {
+                                       this.binary = data.getSession().getValueFactory().createBinary(in);
+                               }
+                       }
+               } else {
+                       throw new IllegalArgumentException(
+                                       "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
+               }
+       }
+
+       @Override
+       public synchronized boolean isOpen() {
+               return open;
+       }
+
+       @Override
+       public synchronized void close() throws IOException {
+               if (isModified()) {
+                       Binary newBinary=null;
+                       try {
+                               Session session = file.getSession();
+                               // byte[] arr = new byte[(int) position];
+                               // toWrite.flip();
+                               // toWrite.get(arr);
+                               fc.position(0);
+                               InputStream in = Channels.newInputStream(fc);
+                               newBinary = session.getValueFactory().createBinary(in);
+                               file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
+                               session.save();
+                               open = false;
+                       } catch (RepositoryException e) {
+                               throw new JcrFsException("Cannot close " + file, e);
+                       }finally{
+                               JcrUtils.closeQuietly(newBinary);
+                               IOUtils.closeQuietly(fc);
+                       }
+               } else {
+                       clearReadState();
+                       open = false;
+               }
+       }
+
+       @Override
+       public int read(ByteBuffer dst) throws IOException {
+               if (isModified()) {
+                       return fc.read(dst);
+               } else {
+
+                       try {
+                               int read;
+//                             int capacity = dst.capacity();
+                               byte[] arr = dst.array();
+                               read = binary.read(arr, position);
+                               //dst.put(arr, 0, read);
+
+                               // try {
+                               // byte[] arr = dst.array();
+                               // read = binary.read(arr, position);
+                               // } catch (UnsupportedOperationException e) {
+                               // int capacity = dst.capacity();
+                               // byte[] arr = new byte[capacity];
+                               // read = binary.read(arr, position);
+                               // dst.put(arr);
+                               // }
+                               if (read != -1)
+                                       position = position + read;
+                               return read;
+                       } catch (RepositoryException e) {
+                               throw new JcrFsException("Cannot read into buffer", e);
+                       }
+               }
+       }
+
+       @Override
+       public int write(ByteBuffer src) throws IOException {
+               int written = getFileChannel().write(src);
+               return written;
+               // int byteCount = src.remaining();
+               // if (toWrite.remaining() < byteCount)
+               // throw new JcrFsException("Write buffer is full");
+               // toWrite.put(src);
+               // if (position < binarySize)
+               // position = binarySize + byteCount;
+               // else
+               // position = position + byteCount;
+               // return byteCount;
+       }
+
+       @Override
+       public long position() throws IOException {
+               if (isModified())
+                       return getFileChannel().position();
+               else
+                       return position;
+       }
+
+       @Override
+       public SeekableByteChannel position(long newPosition) throws IOException {
+               if (isModified()) {
+                       getFileChannel().position(position);
+               } else {
+                       this.position = newPosition;
+               }
+               return this;
+       }
+
+       @Override
+       public long size() throws IOException {
+               if (isModified()) {
+                       return getFileChannel().size();
+               } else {
+                       try {
+                               return binary.getSize();
+                       } catch (RepositoryException e) {
+                               throw new JcrFsException("Cannot get size", e);
+                       }
+               }
+       }
+
+       @Override
+       public SeekableByteChannel truncate(long size) throws IOException {
+               getFileChannel().truncate(size);
+               // if (size != size())
+               // throw new UnsupportedOperationException("Cannot truncate JCR
+               // binary");
+               return this;
+       }
+
+       private FileChannel getFileChannel() throws IOException {
+               try {
+                       if (fc == null) {
+                               Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
+                               fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
+                               ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
+                               fc.transferFrom(readChannel, 0, binary.getSize());
+                               clearReadState();
+                       }
+                       return fc;
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot get temp file channel", e);
+               }
+       }
+
+       private boolean isModified() {
+               return fc != null;
+       }
+       
+       private void clearReadState(){
+               position = -1;
+               JcrUtils.closeQuietly(binary);
+               binary=null;
+       }
+}
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java
new file mode 100644 (file)
index 0000000..e59abbc
--- /dev/null
@@ -0,0 +1,112 @@
+package org.argeo.jcr.fs;
+
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.JcrUtils;
+
+public class JcrBasicfileAttributes implements NodeFileAttributes {
+       private final Node node;
+
+       private FileTime EPOCH = FileTime.fromMillis(0);
+
+       public JcrBasicfileAttributes(Node node) {
+               this.node = node;
+       }
+
+       @Override
+       public FileTime lastModifiedTime() {
+               try {
+                       if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+                               Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant();
+                               return FileTime.from(instant);
+                       }
+                       return EPOCH;
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot get last modified time", e);
+               }
+       }
+
+       @Override
+       public FileTime lastAccessTime() {
+               return lastModifiedTime();
+       }
+
+       @Override
+       public FileTime creationTime() {
+               try {
+                       if (node.isNodeType(NodeType.MIX_CREATED)) {
+                               Instant instant = node.getProperty(Property.JCR_CREATED).getDate().toInstant();
+                               return FileTime.from(instant);
+                       }
+                       return EPOCH;
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot get creation time", e);
+               }
+       }
+
+       @Override
+       public boolean isRegularFile() {
+               try {
+                       return node.isNodeType(NodeType.NT_FILE);
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot check if regular file", e);
+               }
+       }
+
+       @Override
+       public boolean isDirectory() {
+               try {
+                       return node.isNodeType(NodeType.NT_FOLDER);
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot check if directory", e);
+               }
+       }
+
+       @Override
+       public boolean isSymbolicLink() {
+               try {
+                       return node.isNodeType(NodeType.NT_LINKED_FILE);
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot check if linked file", e);
+               }
+       }
+
+       @Override
+       public boolean isOther() {
+               return !(isDirectory() || isRegularFile() || isSymbolicLink());
+       }
+
+       @Override
+       public long size() {
+               if (isRegularFile()) {
+                       Binary binary = null;
+                       try {
+                               binary = node.getNode(Property.JCR_DATA).getProperty(Property.JCR_CONTENT).getBinary();
+                               return binary.getSize();
+                       } catch (RepositoryException e) {
+                               throw new JcrFsException("Cannot check size", e);
+                       } finally {
+                               JcrUtils.closeQuietly(binary);
+                       }
+               }
+               return -1;
+       }
+
+       @Override
+       public Object fileKey() {
+               return null;
+       }
+
+       @Override
+       public Node getNode() {
+               return node;
+       }
+
+}
index 40328e8a093c2fce05b3a84ac7032686052554fc..885eaf19f4b0962b850de47122b36601ad0f0603 100644 (file)
@@ -8,8 +8,10 @@ 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.HashSet;
 import java.util.Set;
 
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.argeo.jcr.JcrUtils;
@@ -51,43 +53,56 @@ public class JcrFileSystem extends FileSystem {
 
        @Override
        public Iterable<Path> getRootDirectories() {
-               return null;
+               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);
+               }
        }
 
        @Override
        public Iterable<FileStore> getFileStores() {
-               // TODO Auto-generated method stub
-               return null;
+               throw new UnsupportedOperationException();
        }
 
        @Override
        public Set<String> supportedFileAttributeViews() {
-               // TODO Auto-generated method stub
-               return null;
+               try {
+                       String[] prefixes = session.getNamespacePrefixes();
+                       Set<String> res = new HashSet<>();
+                       for (String prefix : prefixes)
+                               res.add(prefix);
+                       res.add("basic");
+                       return res;
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot get supported file attributes views", e);
+               }
        }
 
        @Override
        public Path getPath(String first, String... more) {
-               // TODO Auto-generated method stub
-               return null;
+               StringBuilder sb = new StringBuilder(first);
+               // TODO Make it more robust
+               for (String part : more)
+                       sb.append('/').append(part);
+               return new JcrPath(this, sb.toString());
        }
 
        @Override
        public PathMatcher getPathMatcher(String syntaxAndPattern) {
-               // TODO Auto-generated method stub
-               return null;
+               throw new UnsupportedOperationException();
        }
 
        @Override
        public UserPrincipalLookupService getUserPrincipalLookupService() {
-               // TODO Auto-generated method stub
-               return null;
+               throw new UnsupportedOperationException();
        }
 
        @Override
        public WatchService newWatchService() throws IOException {
-               // TODO Auto-generated method stub
-               return null;
+               throw new UnsupportedOperationException();
        }
 
        public Session getSession() {
index 8ea4cca2ea1fe13a8e923dab228b6389756b1087..3e07697dc5c2699661a982af278731d544f82076 100644 (file)
 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.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
 
-       @Override
-       public String getScheme() {
-               return "jcr";
-       }
+import org.apache.commons.io.FileExistsException;
+import org.argeo.jcr.JcrUtils;
 
+public abstract class JcrFileSystemProvider extends FileSystemProvider {
        @Override
-       public FileSystem newFileSystem(URI uri, Map<String, ?> env)
+       public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
                        throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
+               try {
+                       Node node = toNode(path);
+                       if (node == null) {
+                               Node parentNode = toNode(path.getParent());
+                               if (parentNode == null)
+                                       throw new JcrFsException("No parent directory for " + path);
+                               if (!(parentNode.getPath().equals("/") || parentNode.isNodeType(NodeType.NT_FOLDER)))
+                                       throw new JcrFsException("Parent of " + path + " is not a directory");
 
-       @Override
-       public FileSystem getFileSystem(URI uri) {
-               // TODO Auto-generated method stub
-               return null;
+                               node = parentNode.addNode(path.getFileName().toString(), 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);
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot read file", e);
+               }
        }
 
        @Override
-       public Path getPath(URI uri) {
-               // TODO Auto-generated method stub
-               return null;
+       public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
+               try {
+                       Node base = toNode(dir);
+                       return new NodeDirectoryStream((JcrFileSystem) dir.getFileSystem(), base.getNodes(), filter);
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot list directory", e);
+               }
        }
 
        @Override
-       public SeekableByteChannel newByteChannel(Path path,
-                       Set<? extends OpenOption> options, FileAttribute<?>... attrs)
-                       throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public DirectoryStream<Path> newDirectoryStream(Path dir,
-                       Filter<? super Path> filter) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public void createDirectory(Path dir, FileAttribute<?>... attrs)
-                       throws IOException {
-               // TODO Auto-generated method stub
+       public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+               try {
+                       Node node = toNode(dir);
+                       if (node == null) {
+                               Node parent = toNode(dir.getParent());
+                               if (parent == null)
+                                       throw new IOException("Parent of " + dir + " does not exist");
+                               if (!(parent.getPath().equals("/") || parent.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)))
+                                       throw new JcrFsException(dir + " parent is not a directory");
+                               node = parent.addNode(dir.getFileName().toString(), NodeType.NT_FOLDER);
+                               node.addMixin(NodeType.MIX_CREATED);
+                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
+                               node.getSession().save();
+                       } else {
+                               if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
+                                       throw new FileExistsException(dir + " exists and is not a directory");
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot create directory " + dir, e);
+               }
 
        }
 
        @Override
        public void delete(Path path) throws IOException {
-               // TODO Auto-generated method stub
+               try {
+                       Node node = toNode(path);
+                       if (node == null)
+                               throw new NoSuchFileException(path + " does not exist");
+                       Session session = node.getSession();
+                       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();
+                       }
+                       session.save();
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("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 {
+               try {
+                       Node sourceNode = toNode(source);
+                       Node targetNode = toNode(target);
+                       JcrUtils.copy(sourceNode, targetNode);
+                       sourceNode.getSession().save();
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("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 {
+               try {
+                       Node sourceNode = toNode(source);
+                       Session session = sourceNode.getSession();
+                       session.move(sourceNode.getPath(), target.toString());
+                       session.save();
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("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 JcrFsException("Cannot check whether " + path + " and " + path2 + " are same", e);
+                       }
+               }
+
        }
 
        @Override
        public boolean isHidden(Path path) throws IOException {
-               // TODO Auto-generated method stub
                return false;
        }
 
        @Override
        public FileStore getFileStore(Path path) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
+               Session session = ((JcrFileSystem) path.getFileSystem()).getSession();
+               return new WorkSpaceFileStore(session.getWorkspace());
        }
 
        @Override
        public void checkAccess(Path path, AccessMode... modes) throws IOException {
-               // TODO Auto-generated method stub
-
+               try {
+                       Session session = ((JcrFileSystem) path.getFileSystem()).getSession();
+                       if (!session.itemExists(path.toString()))
+                               throw new NoSuchFileException(path + " does not exist");
+                       // TODO check access via JCR api
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot delete " + path, e);
+               }
        }
 
        @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 {
+               try {
+                       // TODO check if assignable
+                       Node node = toNode(path);
+                       return (A) new JcrBasicfileAttributes(node);
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot read basic attributes of " + path, e);
+               }
        }
 
        @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 JcrFsException("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 {
+               try {
+                       Node node = toNode(path);
+                       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());
+                       }
+                       node.getSession().save();
+               } catch (RepositoryException e) {
+                       throw new JcrFsException("Cannot set attribute " + attribute + " on " + path, e);
+               }
+       }
 
+       protected Node toNode(Path path) throws RepositoryException {
+               return ((JcrPath) path).getNode();
        }
 
 }
index e25293517bcc4dfc298b5fec2c2803302a0e2957..6a0c7adfe2d0ebbab10dd1ccf0b648df83ab464c 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.jcr.fs;
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.file.FileSystem;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
@@ -10,147 +11,251 @@ import java.nio.file.WatchEvent.Kind;
 import java.nio.file.WatchEvent.Modifier;
 import java.nio.file.WatchKey;
 import java.nio.file.WatchService;
+import java.util.Arrays;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 
 public class JcrPath implements Path {
-       private JcrFileSystem filesSystem;
-       private String path;
+       private final static String delimStr = "/";
+       private final static char delimChar = '/';
+
+       private final JcrFileSystem fs;
+       private final String[] path;// null means root
+       private final boolean absolute;
+
+       // optim
+       private final int hashCode;
+
+       public JcrPath(JcrFileSystem filesSystem, String path) {
+               // this(filesSystem, path.equals("/") ? null : path.split("/"), path ==
+               // null ? true : path.startsWith("/"));
+               this.fs = filesSystem;
+               if (path == null)
+                       throw new JcrFsException("Path cannot be null");
+               if (path.equals(delimStr)) {// root
+                       this.path = null;
+                       this.absolute = true;
+                       this.hashCode = 0;
+                       return;
+               }
+               this.absolute = path.charAt(0) == delimChar ? true : false;
+               String trimmedPath = path.substring(absolute ? 1 : 0,
+                               path.charAt(path.length() - 1) == delimChar ? path.length() - 1 : path.length());
+               this.path = trimmedPath.split(delimStr);
+               this.hashCode = this.path[this.path.length - 1].hashCode();
+       }
 
-       private Node node;
+       public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
+               this(filesSystem, node.getPath());
+       }
 
-       public JcrPath(JcrFileSystem filesSystem, Node node) {
-               super();
-               this.filesSystem = filesSystem;
-               this.node = node;
+       /** Internal optimisation */
+       private JcrPath(JcrFileSystem filesSystem, String[] path, boolean absolute) {
+               this.fs = filesSystem;
+               this.path = path;
+               this.absolute = path == null ? true : absolute;
+               this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
        }
 
        @Override
        public FileSystem getFileSystem() {
-               return filesSystem;
+               return fs;
        }
 
        @Override
        public boolean isAbsolute() {
-               return path.startsWith("/");
+               return absolute;
        }
 
        @Override
        public Path getRoot() {
                try {
-                       return new JcrPath(filesSystem, node.getSession().getRootNode());
+                       if (path == null)
+                               return this;
+                       return new JcrPath(fs, fs.getSession().getRootNode());
                } catch (RepositoryException e) {
                        throw new JcrFsException("Cannot get root", e);
                }
        }
 
+       @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();
+       }
+
        @Override
        public Path getFileName() {
-               return null;
+               if (path == null)
+                       return null;
+               return new JcrPath(fs, path[path.length - 1]);
        }
 
        @Override
        public Path getParent() {
-               // TODO Auto-generated method stub
-               return null;
+               if (path == null)
+                       return null;
+               if (path.length == 1)// root
+                       return new JcrPath(fs, delimStr);
+               String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
+               return new JcrPath(fs, parentPath, absolute);
        }
 
        @Override
        public int getNameCount() {
-               // TODO Auto-generated method stub
-               return 0;
+               if (path == null)
+                       return 0;
+               return path.length;
        }
 
        @Override
        public Path getName(int index) {
-               // TODO Auto-generated method stub
-               return null;
+               if (path == null)
+                       return null;
+               return new JcrPath(fs, path[index]);
        }
 
        @Override
        public Path subpath(int beginIndex, int endIndex) {
-               // TODO Auto-generated method stub
-               return null;
+               if (path == null)
+                       return null;
+               String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
+               return new JcrPath(fs, parentPath, false);
        }
 
        @Override
        public boolean startsWith(Path other) {
-               // TODO Auto-generated method stub
-               return false;
+               return toString().startsWith(other.toString());
        }
 
        @Override
        public boolean startsWith(String other) {
-               // TODO Auto-generated method stub
-               return false;
+               return toString().startsWith(other);
        }
 
        @Override
        public boolean endsWith(Path other) {
-               // TODO Auto-generated method stub
-               return false;
+               return toString().endsWith(other.toString());
        }
 
        @Override
        public boolean endsWith(String other) {
-               // TODO Auto-generated method stub
-               return false;
+               return toString().endsWith(other);
        }
 
        @Override
        public Path normalize() {
-               // TODO Auto-generated method stub
-               return null;
+               // always normalized
+               return this;
        }
 
        @Override
        public Path resolve(Path other) {
-               // TODO Auto-generated method stub
-               return null;
+               JcrPath otherPath = (JcrPath) other;
+               if (otherPath.isAbsolute())
+                       return other;
+               String[] newPath;
+               if (path == null) {
+                       newPath = new String[otherPath.path.length];
+                       System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length);
+               } else {
+                       newPath = new String[path.length + otherPath.path.length];
+                       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);
        }
 
        @Override
-       public Path resolve(String other) {
-               // TODO Auto-generated method stub
-               return null;
+       public final Path resolve(String other) {
+               return resolve(getFileSystem().getPath(other));
        }
 
        @Override
-       public Path resolveSibling(Path other) {
-               // TODO Auto-generated method stub
-               return null;
+       public final Path resolveSibling(Path other) {
+               if (other == null)
+                       throw new NullPointerException();
+               Path parent = getParent();
+               return (parent == null) ? other : parent.resolve(other);
        }
 
        @Override
-       public Path resolveSibling(String other) {
-               // TODO Auto-generated method stub
-               return null;
+       public final Path resolveSibling(String other) {
+               return resolveSibling(getFileSystem().getPath(other));
+       }
+
+       @Override
+       public final Iterator<Path> iterator() {
+               return new Iterator<Path>() {
+                       private int i = 0;
+
+                       @Override
+                       public boolean hasNext() {
+                               return (i < getNameCount());
+                       }
+
+                       @Override
+                       public Path next() {
+                               if (i < getNameCount()) {
+                                       Path result = getName(i);
+                                       i++;
+                                       return result;
+                               } else {
+                                       throw new NoSuchElementException();
+                               }
+                       }
+
+                       @Override
+                       public void remove() {
+                               throw new UnsupportedOperationException();
+                       }
+               };
        }
 
        @Override
        public Path relativize(Path other) {
-               // TODO Auto-generated method stub
-               return null;
+               if (equals(other))
+                       return new JcrPath(fs, "");
+               if (other.startsWith(this)) {
+                       String p1 = toString();
+                       String p2 = other.toString();
+                       return new JcrPath(fs, p2.substring(p1.length(), p2.length()));
+               }
+               throw new UnsupportedOperationException();
        }
 
        @Override
        public URI toUri() {
-               // TODO Auto-generated method stub
-               return null;
+               try {
+                       return new URI("jcr", toString(), null);
+               } catch (URISyntaxException e) {
+                       throw new JcrFsException("Cannot create URI for " + toString(), e);
+               }
        }
 
        @Override
        public Path toAbsolutePath() {
-               // TODO Auto-generated method stub
-               return null;
+               if (isAbsolute())
+                       return this;
+               return new JcrPath(fs, path, true);
        }
 
        @Override
        public Path toRealPath(LinkOption... options) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
+               return this;
        }
 
        @Override
@@ -159,41 +264,60 @@ public class JcrPath implements Path {
        }
 
        @Override
-       public WatchKey register(WatchService watcher, Kind<?>[] events,
-                       Modifier... modifiers) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>... events)
-                       throws IOException {
+       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
                // TODO Auto-generated method stub
                return null;
        }
 
        @Override
-       public Iterator<Path> iterator() {
+       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
                // TODO Auto-generated method stub
                return null;
        }
 
        @Override
        public int compareTo(Path other) {
-               // TODO Auto-generated method stub
-               return 0;
+               return toString().compareTo(other.toString());
        }
 
-       public Node getNode() {
+       public Node getNode() throws RepositoryException {
                if (!isAbsolute())// TODO default dir
                        throw new JcrFsException("Cannot get node from relative path");
-               try {
-                       if (node == null)
-                               node = filesSystem.getSession().getNode(path);
-                       return node;
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get node", e);
+               String pathStr = toString();
+               Session session = fs.getSession();
+               // TODO synchronize on the session ?
+               if (!session.itemExists(pathStr))
+                       return null;
+               return session.getNode(pathStr);
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof JcrPath))
+                       return false;
+               JcrPath other = (JcrPath) obj;
+               if (path.length != other.path.length)
+                       return false;
+               for (int i = 0; i < path.length; i++) {
+                       if (!path[i].equals(other.path[i]))
+                               return false;
                }
+               return true;
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new JcrPath(fs, toString());
+       }
+
+       @Override
+       protected void finalize() throws Throwable {
+               Arrays.fill(path, null);
        }
 
 }
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/org.argeo.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java
new file mode 100644 (file)
index 0000000..dbf6745
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+public class NodeDirectoryStream implements DirectoryStream<Path> {
+       private final JcrFileSystem fs;
+       private final NodeIterator nodeIterator;
+       private final Filter<? super Path> filter;
+
+       public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Filter<? super Path> filter) {
+               this.fs = fs;
+               this.nodeIterator = nodeIterator;
+               this.filter = filter;
+       }
+
+       @Override
+       public void close() throws IOException {
+       }
+
+       @Override
+       public Iterator<Path> iterator() {
+               return new Iterator<Path>() {
+                       private JcrPath next = null;
+
+                       @Override
+                       public synchronized boolean hasNext() {
+                               if (next != null)
+                                       return true;
+                               nodes: while (nodeIterator.hasNext()) {
+                                       try {
+                                               Node node = nodeIterator.nextNode();
+                                               next = new JcrPath(fs, node);
+                                               if (filter != null) {
+                                                       if (filter.accept(next))
+                                                               break nodes;
+                                               } else
+                                                       break nodes;
+                                       } catch (Exception e) {
+                                               throw new JcrFsException("Could not get next path", e);
+                                       }
+                               }
+                               return next != null;
+                       }
+
+                       @Override
+                       public synchronized Path next() {
+                               if (!hasNext())// make sure has next has been called
+                                       return null;
+                               JcrPath res = next;
+                               next = null;
+                               return res;
+                       }
+
+               };
+       }
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/org.argeo.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java
new file mode 100644 (file)
index 0000000..8054d52
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.jcr.fs;
+
+import java.nio.file.attribute.BasicFileAttributes;
+
+import javax.jcr.Node;
+
+public interface NodeFileAttributes extends BasicFileAttributes {
+       public Node getNode();
+}
diff --git a/org.argeo.jcr/src/org/argeo/jcr/fs/SessionFsProvider.java b/org.argeo.jcr/src/org/argeo/jcr/fs/SessionFsProvider.java
new file mode 100644 (file)
index 0000000..3c5bf16
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.Path;
+import java.util.Map;
+
+import javax.jcr.Session;
+
+/** An FS provider based on a single JCR session (experimental). */
+public class SessionFsProvider extends JcrFileSystemProvider {
+       private Session session;
+       private JcrFileSystem fileSystem;
+
+       public SessionFsProvider(Session session) {
+               this.session = session;
+       }
+
+       @Override
+       public String getScheme() {
+               return "jcr+session";
+       }
+
+       @Override
+       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+               if (fileSystem != null && fileSystem.isOpen())
+                       throw new FileSystemAlreadyExistsException();
+               fileSystem = new JcrFileSystem(this, session) {
+                       boolean open;
+
+                       @Override
+                       public void close() throws IOException {
+                               // prevent the session logout
+                               open = false;
+                       }
+
+                       @Override
+                       public boolean isOpen() {
+                               return open;
+                       }
+
+               };
+               return fileSystem;
+       }
+
+       @Override
+       public FileSystem getFileSystem(URI uri) {
+               return fileSystem;
+       }
+
+       @Override
+       public Path getPath(URI uri) {
+               return new JcrPath(fileSystem, uri.getPath());
+       }
+
+}
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..bdefff3
--- /dev/null
@@ -0,0 +1,67 @@
+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);
+       }
+
+}