SLING-7849 Support file channel operations

delegating file channel operations to file-based file channel
diff --git a/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java b/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java
index 17207b3..c31da7d 100644
--- a/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java
+++ b/src/main/java/org/apache/sling/commons/jcr/file/JcrFileSupportService.java
@@ -38,4 +38,7 @@
     @NotNull
     BasicFileAttributes fromPath(@NotNull final Path path) throws IOException;
 
+    @NotNull
+    Node newFile(@NotNull final Path path) throws RepositoryException;
+
 }
diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java
index 9398ab8..48591df 100644
--- a/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java
+++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/DefaultJcrFileSupportService.java
@@ -118,6 +118,16 @@
         }
     }
 
+    @Override
+    public @NotNull Node newFile(@NotNull Path path) throws RepositoryException {
+        final JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
+        final Session session = fileSystem.getSession();
+        final Node parent = session.getNode(path.getParent().toString());
+        final Node file = parent.addNode(path.getFileName().toString(), "nt:file");
+        final Node content = file.addNode("jcr:content", "nt:resource");
+        return file;
+    }
+
     private static FileTime timeFromProperty(final Node node, final String name) {
         try {
             final Property property = node.getProperty(name);
diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java
index 9f8e7dd..1064a8d 100644
--- a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java
+++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileChannel.java
@@ -19,90 +19,108 @@
 package org.apache.sling.commons.jcr.file.internal;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
 
 import javax.jcr.Binary;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.ValueFactory;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.WRITE;
+
 /**
  * https://wiki.apache.org/jackrabbit/JCR%20Binary%20Usecase
  */
 public class JcrFileChannel extends FileChannel {
 
-    private long position;
-
     private final Node node;
 
+    private final Path file = Files.createTempFile(null, null);
+
+    private final FileChannel fileChannel;
+
     private final Logger logger = LoggerFactory.getLogger(JcrFileChannel.class);
 
-    JcrFileChannel(final Node node) throws RepositoryException {
+    JcrFileChannel(final Node node) throws Exception {
         logger.info("JcrFileChannel: {}", node.getPath());
         this.node = node;
+        fileChannel = init();
     }
 
-    private Binary binary() throws RepositoryException {
+    private Binary getBinary() throws RepositoryException {
         return node.getNode("jcr:content").getProperty("jcr:data").getBinary();
     }
 
-    // TODO
-    @Override
-    public int read(ByteBuffer dst) throws IOException {
-        logger.info("read");
-        /*
+    private void setBinary(final Binary binary) throws RepositoryException {
+        node.getNode("jcr:content").setProperty("jcr:data", binary);
+    }
+
+    private FileChannel init() throws IOException {
+        Binary binary = null;
         try {
-            int read = binary().read(dst.array(), position);
-            position = position + read;
-            logger.info("read {} bytes into array of length {}, new position is {}", read, dst.array().length, position);
-            return read;
+            binary = getBinary();
         } catch (Exception e) {
-            logger.error(e.getMessage(), e);
-            throw new IOException(e);
+            logger.error("reading binary failed: {}", e.getMessage(), e);
         }
-        */
-        return 0;
+        if (binary != null) {
+            try (final InputStream inputStream = binary.getStream()) {
+                Files.copy(inputStream, file, REPLACE_EXISTING);
+            } catch (Exception e) {
+                logger.error("copying binary to file failed: {}", e.getMessage(), e);
+            }
+        }
+        return FileChannel.open(file, READ, WRITE);
     }
 
-    // TODO
     @Override
-    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+    public int read(final ByteBuffer dst) throws IOException {
+        logger.info("read");
+        return fileChannel.read(dst);
+    }
+
+    @Override
+    public long read(final ByteBuffer[] dsts, final int offset, final int length) throws IOException {
         logger.info("read {} {}", offset, length);
-        return 0;
+        return fileChannel.read(dsts, offset, length);
     }
 
-    // TODO
     @Override
-    public int write(ByteBuffer src) throws IOException {
+    public int write(final ByteBuffer src) throws IOException {
         logger.info("write");
-        return 0;
+        return fileChannel.write(src);
     }
 
-    // TODO
     @Override
-    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+    public long write(final ByteBuffer[] srcs, final int offset, final int length) throws IOException {
         logger.info("write {} {}", offset, length);
-        return 0;
+        return fileChannel.write(srcs, offset, length);
     }
 
     @Override
     public long position() throws IOException {
-        logger.info("position: {}", position);
-        return position;
+        logger.info("position");
+        return fileChannel.position();
     }
 
-    // TODO
     @Override
-    public FileChannel position(long newPosition) throws IOException {
-        logger.info("setting position ({}) to new position {}", position, newPosition);
-        this.position = newPosition;
+    public FileChannel position(final long newPosition) throws IOException {
+        logger.info("setting position ({}) to new position {}", fileChannel.position(), newPosition);
+        fileChannel.position(newPosition);
         return this;
     }
 
@@ -111,52 +129,48 @@
     public long size() throws IOException {
         logger.info("size");
         try {
-            return binary().getSize();
+            return getBinary().getSize();
         } catch (Exception e) {
             logger.error(e.getMessage(), e);
             throw new IOException(e);
         }
     }
 
-    // TODO
     @Override
-    public FileChannel truncate(long size) throws IOException {
+    public FileChannel truncate(final long size) throws IOException {
         logger.info("truncate", size);
+        fileChannel.truncate(size);
         return this;
     }
 
     // TODO
     @Override
-    public void force(boolean metaData) throws IOException {
+    public void force(final boolean metaData) throws IOException {
         logger.info("force {}", metaData);
     }
 
-    // TODO
     @Override
-    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
+    public long transferTo(final long position, final long count, final WritableByteChannel target) throws IOException {
         logger.info("transferTo {} {}", position, count);
-        return 0;
+        return fileChannel.transferTo(position, count, target);
     }
 
-    // TODO
     @Override
-    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+    public long transferFrom(final ReadableByteChannel src, final long position, final long count) throws IOException {
         logger.info("transferFrom {} {}", position, count);
-        return 0;
+        return fileChannel.transferFrom(src, position, count);
     }
 
-    // TODO
     @Override
-    public int read(ByteBuffer dst, long position) throws IOException {
+    public int read(final ByteBuffer dst, final long position) throws IOException {
         logger.info("read {}", position);
-        return 0;
+        return fileChannel.read(dst, position);
     }
 
-    // TODO
     @Override
-    public int write(ByteBuffer src, long position) throws IOException {
+    public int write(final ByteBuffer src, final long position) throws IOException {
         logger.info("write {}", position);
-        return 0;
+        return fileChannel.write(src, position);
     }
 
     // TODO
@@ -180,10 +194,20 @@
         return null;
     }
 
-    // TODO
     @Override
     protected void implCloseChannel() throws IOException {
         logger.info("implCloseChannel");
+        fileChannel.close();
+        try (final InputStream inputStream = Files.newInputStream(file, READ, DELETE_ON_CLOSE)) {
+            final Session session = node.getSession();
+            final ValueFactory valueFactory = session.getValueFactory();
+            final Binary binary = valueFactory.createBinary(inputStream);
+            setBinary(binary);
+            session.save();
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            throw new IOException(e);
+        }
     }
 
 }
diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java
index 722f8c3..28da678 100644
--- a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java
+++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystem.java
@@ -26,11 +26,9 @@
 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.Collections;
 import java.util.Set;
 
-import javax.jcr.Node;
 import javax.jcr.Session;
 
 import org.jetbrains.annotations.NotNull;
@@ -68,6 +66,12 @@
     public void close() throws IOException {
         logger.info("close");
         provider.removeFromCache(this);
+        try {
+            session.save();
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            throw new IOException(e);
+        }
         session.logout();
     }
 
diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java
index 0de4955..59111d3 100644
--- a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java
+++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrFileSystemProvider.java
@@ -139,12 +139,19 @@
     }
 
     // TODO
-    public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+    public FileChannel newFileChannel(final Path path, final Set<? extends OpenOption> options, final FileAttribute<?>... attrs) throws IOException {
         logger.info("newFileChannel");
         try {
-            final Node node = PathUtil.toNode(path);
+            final JcrPath jcrPath = (JcrPath) path;
+            final Node node;
+            if (jcrPath.toFile().exists()) {
+                node = PathUtil.toNode(path);
+            } else {
+                node = jcrFileSupportService.newFile(path);
+            }
             return new JcrFileChannel(node);
         } catch (Exception e) {
+            logger.error(e.getMessage(), e);
             throw new IOException(e);
         }
     }
@@ -157,6 +164,7 @@
             final Node node = PathUtil.toNode(path);
             return new JcrFileChannel(node);
         } catch (Exception e) {
+            logger.error(e.getMessage(), e);
             throw new IOException(e);
         }
     }
diff --git a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java
index 7108186..1c7be5c 100644
--- a/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java
+++ b/src/main/java/org/apache/sling/commons/jcr/file/internal/JcrPath.java
@@ -243,7 +243,7 @@
     }
 
     @Override
-    public File toFile() {
+    public JcrFile toFile() {
         logger.info("to file: {}", path);
         return new JcrFile(fileSystem, path);
     }