Start integrating GCR and JCR (not yet working)
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Dec 2021 10:04:24 +0000 (11:04 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Dec 2021 10:04:24 +0000 (11:04 +0100)
28 files changed:
org.argeo.api/src/org/argeo/api/gcr/AbstractContent.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/Content.java
org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java
org.argeo.api/src/org/argeo/api/gcr/ContentSession.java
org.argeo.api/src/org/argeo/api/gcr/ContentStore.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/Contents.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java
org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java [new file with mode: 0644]
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java

diff --git a/org.argeo.api/src/org/argeo/api/gcr/AbstractContent.java b/org.argeo.api/src/org/argeo/api/gcr/AbstractContent.java
deleted file mode 100644 (file)
index f318b99..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.api.gcr;
-
-import java.util.AbstractMap;
-import java.util.HashSet;
-import java.util.Set;
-
-public abstract class AbstractContent extends AbstractMap<String, Object> implements Content {
-
-       @Override
-       public Set<Entry<String, Object>> entrySet() {
-               Set<Entry<String, Object>> result = new HashSet<>();
-               for (String key : keys()) {
-                       Entry<String, Object> entry = new Entry<String, Object>() {
-
-                               @Override
-                               public String getKey() {
-                                       return key;
-                               }
-
-                               @Override
-                               public Object getValue() {
-                                       // TODO check type
-                                       return get(key, Object.class);
-                               }
-
-                               @Override
-                               public Object setValue(Object value) {
-                                       throw new UnsupportedOperationException();
-                               }
-
-                       };
-                       result.add(entry);
-               }
-               return result;
-       }
-
-       protected abstract Iterable<String> keys();
-}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java b/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java
new file mode 100644 (file)
index 0000000..a628cda
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.api.gcr;
+
+/**
+ * An attribute type MUST consistently parse a string to an object so that
+ * <code>parse(obj.toString()).equals(obj)</code> is verified.
+ * {@link #format(Object)} can be overridden to provide more efficient
+ * implementations but the returned
+ * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
+ * is verified.
+ */
+public interface AttributeFormatter<T> {
+       /** Parses a String to a Java object. */
+       T parse(String str) throws IllegalArgumentException;
+
+       /** Default implementation returns {@link Object#toString()} on the argument. */
+       default String format(T obj) {
+               return obj.toString();
+       }
+}
index 34f11758aa08f2e45d9536875fe6638ddd72098c..b032aa7b888020dce1779b34d0434ee36613fd28 100644 (file)
@@ -13,7 +13,7 @@ public interface Content extends Iterable<Content>, Map<String, Object> {
 
        <A> A get(String key, Class<A> clss);
 
-       ContentSession getSession();
+//     ContentSession getSession();
 
        /*
         * DEFAULT METHODS
@@ -29,12 +29,12 @@ public interface Content extends Iterable<Content>, Map<String, Object> {
                return get(key, String.class);
        }
 
-       default String attr(Enum<?> key) {
-               return attr(key.name());
+       default String attr(Object key) {
+               return key != null ? attr(key.toString()) : attr(null);
        }
 
-       default <A> A get(Enum<?> key, Class<A> clss) {
-               return get(key.name(), clss);
+       default <A> A get(Object key, Class<A> clss) {
+               return key != null ? get(key.toString(), clss) : get(null, clss);
        }
 
        /*
index 3349b30a1acef5b32c3351b04cbb7df6b08d2e5b..0807075ced23eb3a62767a395803604d674cce22 100644 (file)
@@ -1,6 +1,12 @@
 package org.argeo.api.gcr;
 
+import java.util.Locale;
 import java.util.function.Supplier;
 
+/**
+ * A content repository is an actually running implementation of various kind of
+ * content system. It allows a pre-authenticated caller to open a session.
+ */
 public interface ContentRepository extends Supplier<ContentSession> {
+       ContentSession get(Locale locale);
 }
index 78325b135b41ed8a63607fd48af815065cd3dfe5..b7ffcd0431d0f9d6c5a8f8af3ba7160376d1a13c 100644 (file)
@@ -1,7 +1,11 @@
 package org.argeo.api.gcr;
 
-import java.util.function.Supplier;
+import java.util.Locale;
 
-public interface ContentSession extends Supplier<Content> {
+import javax.security.auth.Subject;
 
+public interface ContentSession {
+       Subject getSubject();
+
+       Locale getLocale();
 }
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java b/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java
new file mode 100644 (file)
index 0000000..c9c90bc
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.api.gcr;
+
+public interface ContentStore {
+
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java
new file mode 100644 (file)
index 0000000..04ac11a
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.api.gcr;
+
+public interface ContentSystem {
+
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java
new file mode 100644 (file)
index 0000000..4f0d66d
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.gcr;
+
+import java.util.function.Supplier;
+
+public interface ContentSystemProvider extends Supplier<Content> {
+
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java b/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java
new file mode 100644 (file)
index 0000000..285669c
--- /dev/null
@@ -0,0 +1,75 @@
+package org.argeo.api.gcr;
+
+import java.io.PrintStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+public class ContentUtils {
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
+               traverse(content, doIt, 0);
+       }
+
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
+               doIt.accept(content, currentDepth);
+               int nextDepth = currentDepth + 1;
+               for (Content child : content) {
+                       traverse(child, doIt, nextDepth);
+               }
+       }
+
+       public static void print(Content content, PrintStream out, int depth, boolean printText) {
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < depth; i++) {
+                       sb.append("  ");
+               }
+               String prefix = sb.toString();
+               out.println(prefix + content.getName());
+               for (String key : content.keySet()) {
+                       out.println(prefix + " " + key + "=" + content.get(key));
+               }
+               if (printText) {
+                       if (content.hasText()) {
+                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
+                       }
+               }
+       }
+
+       public static URI bytesToDataURI(byte[] arr) {
+               String base64Str = Base64.getEncoder().encodeToString(arr);
+               try {
+                       final String PREFIX = "data:application/octet-stream;base64,";
+                       return new URI(PREFIX + base64Str);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
+               }
+
+       }
+
+       public static byte[] bytesFromDataURI(URI uri) {
+               if (!"data".equals(uri.getScheme()))
+                       throw new IllegalArgumentException("URI must have 'data' as a scheme");
+               String schemeSpecificPart = uri.getSchemeSpecificPart();
+               int commaIndex = schemeSpecificPart.indexOf(',');
+               String prefix = schemeSpecificPart.substring(0, commaIndex);
+               List<String> info = Arrays.asList(prefix.split(";"));
+               if (!info.contains("base64"))
+                       throw new IllegalArgumentException("URI must specify base64");
+
+               String base64Str = uri.toString().substring(commaIndex);
+               return Base64.getDecoder().decode(base64Str);
+
+       }
+
+       public static <T> boolean isString(T t) {
+               return t instanceof String;
+       }
+
+       /** Singleton. */
+       private ContentUtils() {
+
+       }
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/Contents.java b/org.argeo.api/src/org/argeo/api/gcr/Contents.java
deleted file mode 100644 (file)
index 62b1e9b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.api.gcr;
-
-import java.io.PrintStream;
-import java.util.function.BiConsumer;
-
-public class Contents {
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
-               traverse(content, doIt, 0);
-       }
-
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
-               doIt.accept(content, currentDepth);
-               int nextDepth = currentDepth + 1;
-               for (Content child : content) {
-                       traverse(child, doIt, nextDepth);
-               }
-       }
-
-       public static void print(Content content, PrintStream out, int depth, boolean printText) {
-               StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < depth; i++) {
-                       sb.append("  ");
-               }
-               String prefix = sb.toString();
-               out.println(prefix + content.getName());
-               for (String key : content.keySet()) {
-                       out.println(prefix + " " + key + "=" + content.get(key));
-               }
-               if (printText) {
-                       if (content.hasText()) {
-                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
-                       }
-               }
-       }
-
-       public static <T> boolean isString(T t) {
-               return t instanceof String;
-       }
-
-       /** Singleton. */
-       private Contents() {
-
-       }
-}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java b/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java
new file mode 100644 (file)
index 0000000..b079aa5
--- /dev/null
@@ -0,0 +1,168 @@
+package org.argeo.api.gcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.UUID;
+
+/**
+ * Minimal standard attribute types that MUST be supported. All related classes
+ * belong to java.base and can be implicitly derived form a given
+ * <code>String<code>.
+ */
+public enum StandardAttributeType {
+       BOOLEAN(Boolean.class, new BooleanFormatter()), //
+       // TODO Also support INTEGER ?
+       LONG(Long.class, new LongFormatter()), //
+       DOUBLE(Double.class, new DoubleFormatter()), //
+       INSTANT(Instant.class, new InstantFormatter()), //
+       UUID(UUID.class, new UuidFormatter()), //
+       URI(URI.class, new UriFormatter()), //
+       STRING(String.class, new StringFormatter()), //
+       ;
+
+       private <T> StandardAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+               this.clss = clss;
+               this.formatter = formatter;
+       }
+
+       private final Class<?> clss;
+       private final AttributeFormatter<?> formatter;
+
+       public Class<?> getClss() {
+               return clss;
+       }
+
+       public AttributeFormatter<?> getFormatter() {
+               return formatter;
+       }
+
+       static Object parse(String str) {
+               if (str == null)
+                       return null;
+               // order IS important
+               try {
+                       if (str.length() == 4 || str.length() == 5)
+                               return BOOLEAN.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return LONG.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return DOUBLE.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return INSTANT.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       if (str.length() == 36)
+                               return UUID.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       java.net.URI uri = (java.net.URI) URI.getFormatter().parse(str);
+                       if (uri.getScheme() != null)
+                               return uri;
+                       String path = uri.getPath();
+                       if (path.indexOf('/') >= 0)
+                               return uri;
+                       // if it is not clearly a path, we will consider it as a string
+                       // because their is no way to distinguish between 'any_string'
+                       // and 'any_file_name'.
+                       // Note that providing ./any_file_name would result in an equivalent URI
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+
+               // default
+               return STRING.getFormatter().parse(str);
+       }
+
+       static class StringFormatter implements AttributeFormatter<String> {
+
+               @Override
+               public String parse(String str) {
+                       return str;
+               }
+
+               @Override
+               public String format(String obj) {
+                       return obj;
+               }
+
+       }
+
+       static class BooleanFormatter implements AttributeFormatter<Boolean> {
+
+               /**
+                * @param str must be exactly equals to either 'true' or 'false' (different
+                *            contract than {@link Boolean#parseBoolean(String)}.
+                */
+               @Override
+               public Boolean parse(String str) throws IllegalArgumentException {
+                       if ("true".equals(str))
+                               return Boolean.TRUE;
+                       if ("false".equals(str))
+                               return Boolean.FALSE;
+                       throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
+               }
+       }
+
+       static class LongFormatter implements AttributeFormatter<Long> {
+               @Override
+               public Long parse(String str) throws NumberFormatException {
+                       return Long.parseLong(str);
+               }
+       }
+
+       static class DoubleFormatter implements AttributeFormatter<Double> {
+
+               @Override
+               public Double parse(String str) throws NumberFormatException {
+                       return Double.parseDouble(str);
+               }
+       }
+
+       static class InstantFormatter implements AttributeFormatter<Instant> {
+
+               @Override
+               public Instant parse(String str) throws IllegalArgumentException {
+                       try {
+                               return Instant.parse(str);
+                       } catch (DateTimeParseException e) {
+                               throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
+                       }
+               }
+       }
+
+       static class UuidFormatter implements AttributeFormatter<UUID> {
+
+               @Override
+               public UUID parse(String str) throws IllegalArgumentException {
+                       return java.util.UUID.fromString(str);
+               }
+       }
+
+       static class UriFormatter implements AttributeFormatter<URI> {
+
+               @Override
+               public URI parse(String str) throws IllegalArgumentException {
+                       try {
+                               return new URI(str);
+                       } catch (URISyntaxException e) {
+                               throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
+                       }
+               }
+
+       }
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java
new file mode 100644 (file)
index 0000000..c83fe38
--- /dev/null
@@ -0,0 +1,387 @@
+package org.argeo.api.gcr.fs;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+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;
+
+public abstract class AbstractFsPath<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
+       private final FS fs;
+       /** null for non absolute paths */
+       private final ST fileStore;
+
+       private final String[] segments;// null means root
+       private final boolean absolute;
+
+       private final String separator;
+
+       // optim
+       private final int hashCode;
+
+       public AbstractFsPath(FS filesSystem, String path) {
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               this.fs = filesSystem;
+               this.separator = fs.getSeparator();
+               // TODO deal with both path and separator being empty strings
+               if (path.equals(separator)) {// root
+                       this.segments = null;
+                       this.absolute = true;
+                       this.hashCode = 0;
+                       this.fileStore = fs.getBaseFileStore();
+                       return;
+               } else if (path.equals("")) {// empty path
+                       this.segments = new String[] { "" };
+                       this.absolute = false;
+                       this.hashCode = "".hashCode();
+                       this.fileStore = null;
+                       return;
+               }
+
+               this.absolute = path.startsWith(toStringRoot());
+
+               String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
+                               path.endsWith(separator) ? path.length() - separator.length() : path.length());
+               this.segments = trimmedPath.split(separator);
+               // clean up
+               for (int i = 0; i < this.segments.length; i++) {
+                       this.segments[i] = cleanUpSegment(this.segments[i]);
+               }
+               this.hashCode = this.segments[this.segments.length - 1].hashCode();
+
+               this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
+       }
+
+       protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
+               this.segments = segments;
+               this.absolute = absolute;
+               this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
+               this.separator = filesSystem.getSeparator();
+//             super(path, path == null ? true : absolute, filesSystem.getSeparator());
+//             assert path == null ? absolute == true : true;
+               this.fs = filesSystem;
+//             this.path = path;
+//             this.absolute = path == null ? true : absolute;
+               if (isAbsolute() && fileStore == null)
+                       throw new IllegalArgumentException("Absolute path requires a file store");
+               if (!isAbsolute() && fileStore != null)
+                       throw new IllegalArgumentException("A file store should not be provided for a relative path");
+               this.fileStore = fileStore;
+               assert !(absolute && fileStore == null);
+       }
+
+       protected Path retrieve(String path) {
+               return getFileSystem().getPath(path);
+       }
+
+       @Override
+       public FS getFileSystem() {
+               return fs;
+       }
+
+       public ST getFileStore() {
+               return fileStore;
+       }
+
+       @Override
+       public boolean isAbsolute() {
+               return absolute;
+       }
+
+       @Override
+       public URI toUri() {
+               try {
+                       return new URI(fs.provider().getScheme(), toString(), null);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot create URI for " + toString(), e);
+               }
+       }
+
+       @Override
+       public Path toAbsolutePath() {
+               if (isAbsolute())
+                       return this;
+               // FIXME it doesn't seem right
+               return newInstance(getSegments(), true);
+       }
+
+       @Override
+       public Path toRealPath(LinkOption... options) throws IOException {
+               return this;
+       }
+
+       @Override
+       public File toFile() {
+               throw new UnsupportedOperationException();
+       }
+
+       /*
+        * PATH OPERATIONS
+        */
+       public final Path resolveSibling(Path other) {
+               if (other == null)
+                       throw new NullPointerException();
+               Path parent = getParent();
+               return (parent == null) ? other : parent.resolve(other);
+       }
+
+       @Override
+       public final Path resolveSibling(String other) {
+               return resolveSibling(getFileSystem().getPath(other));
+       }
+
+       public final Path resolve(String other) {
+               return resolve(retrieve(other));
+       }
+
+       public boolean startsWith(Path other) {
+               return toString().startsWith(other.toString());
+       }
+
+       public boolean endsWith(Path other) {
+               return toString().endsWith(other.toString());
+       }
+
+       @Override
+       public Path normalize() {
+               // always normalized
+               return this;
+       }
+
+       @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 int compareTo(Path other) {
+               return toString().compareTo(other.toString());
+       }
+
+       public Path resolve(Path other) {
+               AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
+               if (otherPath.isAbsolute())
+                       return other;
+               String[] newPath;
+               if (isRoot()) {
+                       newPath = new String[otherPath.segments.length];
+                       System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
+               } else {
+                       newPath = new String[segments.length + otherPath.segments.length];
+                       System.arraycopy(segments, 0, newPath, 0, segments.length);
+                       System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
+               }
+               if (!absolute)
+                       return newInstance(newPath, absolute);
+               else {
+                       return newInstance(toString(newPath));
+               }
+       }
+
+       public Path relativize(Path other) {
+               if (equals(other))
+                       return newInstance("");
+               if (other.toString().startsWith(this.toString())) {
+                       String p1 = toString();
+                       String p2 = other.toString();
+                       String relative = p2.substring(p1.length(), p2.length());
+                       if (relative.charAt(0) == '/')
+                               relative = relative.substring(1);
+                       return newInstance(relative);
+               }
+               throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+       }
+
+       /*
+        * FACTORIES
+        */
+       protected abstract AbstractFsPath<FS, ST> newInstance(String path);
+
+       protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
+
+       /*
+        * CUSTOMISATIONS
+        */
+       protected String toStringRoot() {
+               return separator;
+       }
+
+       protected String cleanUpSegment(String segment) {
+               return segment;
+       }
+
+       protected boolean isRoot() {
+               return segments == null;
+       }
+
+       protected boolean isEmpty() {
+               return segments.length == 1 && "".equals(segments[0]);
+       }
+
+       /*
+        * PATH OPERATIONS
+        */
+       public AbstractFsPath<FS, ST> getRoot() {
+               return newInstance(toStringRoot());
+       }
+
+       public AbstractFsPath<FS, ST> getParent() {
+               if (isRoot())
+                       return null;
+               // FIXME empty path?
+               if (segments.length == 1)// first level
+                       return newInstance(toStringRoot());
+               String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
+               if (!absolute)
+                       return newInstance(parentPath, absolute);
+               else
+                       return newInstance(toString(parentPath));
+       }
+
+       public AbstractFsPath<FS, ST> getFileName() {
+               if (isRoot())
+                       return null;
+               return newInstance(segments[segments.length - 1]);
+       }
+
+       public int getNameCount() {
+               if (isRoot())
+                       return 0;
+               return segments.length;
+       }
+
+       public AbstractFsPath<FS, ST> getName(int index) {
+               if (isRoot())
+                       return null;
+               return newInstance(segments[index]);
+       }
+
+       public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
+               if (isRoot())
+                       return null;
+               String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
+               return newInstance(parentPath, false);
+       }
+
+       public boolean startsWith(String other) {
+               return toString().startsWith(other);
+       }
+
+       public boolean endsWith(String other) {
+               return toString().endsWith(other);
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected String toString(String[] path) {
+               if (isRoot())
+                       return toStringRoot();
+               StringBuilder sb = new StringBuilder();
+               if (isAbsolute())
+                       sb.append(separator);
+               for (int i = 0; i < path.length; i++) {
+                       if (i != 0)
+                               sb.append(separator);
+                       sb.append(path[i]);
+               }
+               return sb.toString();
+       }
+
+       @Override
+       public String toString() {
+               return toString(segments);
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof AbstractFsPath))
+                       return false;
+               AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
+
+               if (isRoot()) {// root
+                       if (other.isRoot())// root
+                               return true;
+                       else
+                               return false;
+               } else {
+                       if (other.isRoot())// root
+                               return false;
+               }
+               // non root
+               if (segments.length != other.segments.length)
+                       return false;
+               for (int i = 0; i < segments.length; i++) {
+                       if (!segments[i].equals(other.segments[i]))
+                               return false;
+               }
+               return true;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return newInstance(toString());
+       }
+
+       /*
+        * GETTERS / SETTERS
+        */
+       protected String[] getSegments() {
+               return segments;
+       }
+
+       protected String getSeparator() {
+               return separator;
+       }
+
+       /*
+        * UNSUPPORTED
+        */
+       @Override
+       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java
new file mode 100644 (file)
index 0000000..0088734
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.gcr.fs;
+
+import java.nio.file.FileStore;
+
+public abstract class AbstractFsStore extends FileStore {
+
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java
new file mode 100644 (file)
index 0000000..36369ca
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.gcr.fs;
+
+import java.nio.file.FileSystem;
+
+public abstract class AbstractFsSystem<ST extends AbstractFsStore> extends FileSystem {
+       public abstract ST getBaseFileStore();
+
+       public abstract ST getFileStore(String path);
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java b/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java
new file mode 100644 (file)
index 0000000..2da186e
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.api.gcr.spi;
+
+import java.util.AbstractMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.argeo.api.gcr.Content;
+
+public abstract class AbstractContent extends AbstractMap<String, Object> implements Content {
+
+       @Override
+       public Set<Entry<String, Object>> entrySet() {
+               Set<Entry<String, Object>> result = new HashSet<>();
+               for (String key : keys()) {
+                       Entry<String, Object> entry = new Entry<String, Object>() {
+
+                               @Override
+                               public String getKey() {
+                                       return key;
+                               }
+
+                               @Override
+                               public Object getValue() {
+                                       // TODO check type
+                                       return get(key, Object.class);
+                               }
+
+                               @Override
+                               public Object setValue(Object value) {
+                                       throw new UnsupportedOperationException();
+                               }
+
+                       };
+                       result.add(entry);
+               }
+               return result;
+       }
+
+       protected abstract Iterable<String> keys();
+       
+       /*
+        * UTILITIES
+        */
+       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+               // check whether clss is Object.class 
+               return clss.isAssignableFrom(Object.class);
+       }
+}
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java
new file mode 100644 (file)
index 0000000..b136a01
--- /dev/null
@@ -0,0 +1,155 @@
+package org.argeo.cms.jcr.gcr;
+
+import java.util.Calendar;
+import java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.spi.AbstractContent;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+public class JcrContent extends AbstractContent {
+       private JcrContentSession contentSession;
+       private Node jcrNode;
+
+       protected JcrContent(JcrContentSession contentSession, Node node) {
+               this.contentSession = contentSession;
+               this.jcrNode = node;
+       }
+
+       @Override
+       public String getName() {
+               return Jcr.getName(jcrNode);
+       }
+
+       @Override
+       public <A> A get(String key, Class<A> clss) {
+               if (isDefaultAttrTypeRequested(clss)) {
+                       return (A) get(jcrNode, key);
+               }
+               return (A) Jcr.get(jcrNode, key);
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               try {
+                       return new JcrContentIterator(contentSession, jcrNode.getNodes());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot list children of " + jcrNode, e);
+               }
+       }
+
+       @Override
+       protected Iterable<String> keys() {
+               return new Iterable<String>() {
+
+                       @Override
+                       public Iterator<String> iterator() {
+                               try {
+                                       PropertyIterator propertyIterator = jcrNode.getProperties();
+                                       return new JcrKeyIterator(contentSession, propertyIterator);
+                               } catch (RepositoryException e) {
+                                       throw new JcrException("Cannot retrive properties from " + jcrNode, e);
+                               }
+                       }
+               };
+       }
+
+       public Node getJcrNode() {
+               return jcrNode;
+       }
+
+       /** Cast to a standard Java object. */
+       static Object get(Node node, String property) {
+               try {
+                       Value value = node.getProperty(property).getValue();
+                       switch (value.getType()) {
+                       case PropertyType.STRING:
+                               return value.getString();
+                       case PropertyType.DOUBLE:
+                               return (Double) value.getDouble();
+                       case PropertyType.LONG:
+                               return (Long) value.getLong();
+                       case PropertyType.BOOLEAN:
+                               return (Boolean) value.getBoolean();
+                       case PropertyType.DATE:
+                               Calendar calendar = value.getDate();
+                               return calendar.toInstant();
+                       case PropertyType.BINARY:
+                               throw new IllegalArgumentException("Binary is not supported as an attribute");
+                       default:
+                               return value.getString();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
+               }
+       }
+
+       static class JcrContentIterator implements Iterator<Content> {
+               private final JcrContentSession contentSession;
+               private final NodeIterator nodeIterator;
+               // we keep track in order to be able to delete it
+               private JcrContent current = null;
+
+               protected JcrContentIterator(JcrContentSession contentSession, NodeIterator nodeIterator) {
+                       this.contentSession = contentSession;
+                       this.nodeIterator = nodeIterator;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return nodeIterator.hasNext();
+               }
+
+               @Override
+               public Content next() {
+                       current = new JcrContent(contentSession, nodeIterator.nextNode());
+                       return current;
+               }
+
+               @Override
+               public void remove() {
+                       if (current != null) {
+                               // current.getJcrNode().remove();
+                       }
+                       throw new UnsupportedOperationException();
+               }
+
+       }
+
+       static class JcrKeyIterator implements Iterator<String> {
+               private final JcrContentSession contentSession;
+               private final PropertyIterator propertyIterator;
+
+               protected JcrKeyIterator(JcrContentSession contentSession, PropertyIterator propertyIterator) {
+                       this.contentSession = contentSession;
+                       this.propertyIterator = propertyIterator;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return propertyIterator.hasNext();
+               }
+
+               @Override
+               public String next() {
+                       Property property = null;
+                       try {
+                               property = propertyIterator.nextProperty();
+                               // TODO map standard property names
+                               return property.getName();
+                       } catch (RepositoryException e) {
+                               throw new JcrException("Cannot retrieve property " + property, null);
+                       }
+               }
+
+       }
+}
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java
new file mode 100644 (file)
index 0000000..057ed30
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.cms.jcr.gcr;
+
+import java.security.AccessController;
+import java.util.Locale;
+
+import javax.jcr.Repository;
+import javax.security.auth.Subject;
+
+import org.argeo.api.gcr.ContentRepository;
+import org.argeo.api.gcr.ContentSession;
+
+public class JcrContentRepository implements ContentRepository {
+       private Repository jcrRepository;
+
+       @Override
+       public ContentSession get() {
+               // TODO retrieve locale from Subject?
+               return get(Locale.getDefault());
+       }
+
+       @Override
+       public ContentSession get(Locale locale) {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               return new JcrContentSession(jcrRepository, subject, locale);
+       }
+
+       public Repository getJcrRepository() {
+               return jcrRepository;
+       }
+
+       public void setJcrRepository(Repository jcrRepository) {
+               this.jcrRepository = jcrRepository;
+       }
+
+}
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java
new file mode 100644 (file)
index 0000000..98ecbc8
--- /dev/null
@@ -0,0 +1,47 @@
+package org.argeo.cms.jcr.gcr;
+
+import java.security.PrivilegedAction;
+import java.util.Locale;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+
+import org.argeo.api.gcr.ContentSession;
+import org.argeo.jcr.JcrException;
+
+public class JcrContentSession implements ContentSession {
+       private Repository jcrRepository;
+       private Subject subject;
+       private Locale locale;
+       private Session jcrSession;
+
+       protected JcrContentSession(Repository jcrRepository, Subject subject, Locale locale) {
+               this.jcrRepository = jcrRepository;
+               this.subject = subject;
+               this.locale = locale;
+               this.jcrSession = Subject.doAs(this.subject, (PrivilegedAction<Session>) () -> {
+                       try {
+                               return jcrRepository.login();
+                       } catch (RepositoryException e) {
+                               throw new JcrException("Cannot log in to repository", e);
+                       }
+               });
+       }
+
+       @Override
+       public Subject getSubject() {
+               return subject;
+       }
+
+       @Override
+       public Locale getLocale() {
+               return locale;
+       }
+
+       public Session getJcrSession() {
+               return jcrSession;
+       }
+
+}
index 72e325d35a40c40ad22a712ae7561fd33ae6ed87..bf5de1260f93e11bb2b7d47baa31ea3e3eb8460b 100644 (file)
@@ -598,6 +598,16 @@ public class Jcr {
                        throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
                }
        }
+       
+       public static <T> T getAs(Node node, String property, Class<T> clss) {
+               if(String.class.isAssignableFrom(clss)) {
+                       return (T)get(node,property);
+               }       else    if(Long.class.isAssignableFrom(clss)) {
+                       return (T)get(node,property);
+               }else {
+                       throw new IllegalArgumentException("Unsupported format "+clss);
+               }
+       }
 
        /**
         * Get a multiple property as a list, doing a best effort to cast it as the
index 3d538e8bde485963f1fc0d23b58900f74a2eac9b..3d0fcfe668b9a422bdeb528cde573d6dcc608077 100644 (file)
@@ -23,10 +23,12 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.nodetype.NodeType;
 
+import org.argeo.api.gcr.fs.AbstractFsStore;
+import org.argeo.api.gcr.fs.AbstractFsSystem;
 import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrUtils;
 
-public class JcrFileSystem extends FileSystem {
+public class JcrFileSystem extends AbstractFsSystem<WorkspaceFileStore> {
        private final JcrFileSystemProvider provider;
 
        private final Repository repository;
index 1a4d747067b1a20baa29d35683256c9efa3dde68..8782be9eed5b5659b52ea8aa806205a1bb3722dc 100644 (file)
@@ -1,73 +1,63 @@
 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;
-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 org.argeo.api.gcr.fs.AbstractFsPath;
+
 /** A {@link Path} which contains a reference to a JCR {@link Node}. */
-public class JcrPath implements Path {
+public class JcrPath extends AbstractFsPath<JcrFileSystem, WorkspaceFileStore> {
        final static String separator = "/";
        final static char separatorChar = '/';
 
-       private final JcrFileSystem fs;
-       /** null for non absolute paths */
-       private final WorkspaceFileStore fileStore;
-       private final String[] path;// null means root
-       private final boolean absolute;
-
-       // optim
-       private final int hashCode;
+//     private final JcrFileSystem fs;
+//     /** null for non absolute paths */
+//     private final WorkspaceFileStore fileStore;
+//     private final String[] path;// null means root
+//     private final boolean absolute;
+//
+//     // optim
+//     private final int hashCode;
 
        public JcrPath(JcrFileSystem filesSystem, String path) {
-               this.fs = filesSystem;
-               if (path == null)
-                       throw new JcrFsException("Path cannot be null");
-               if (path.equals(separator)) {// root
-                       this.path = null;
-                       this.absolute = true;
-                       this.hashCode = 0;
-                       this.fileStore = fs.getBaseFileStore();
-                       return;
-               } else if (path.equals("")) {// empty path
-                       this.path = new String[] { "" };
-                       this.absolute = false;
-                       this.fileStore = null;
-                       this.hashCode = "".hashCode();
-                       return;
-               }
-
-               if (path.equals("~")) {// home
-                       path = filesSystem.getUserHomePath();
-                       if (path == null)
-                               throw new JcrFsException("No home directory available");
-               }
-
-               this.absolute = path.charAt(0) == separatorChar ? true : false;
-
-               this.fileStore = absolute ? fs.getFileStore(path) : null;
-
-               String trimmedPath = path.substring(absolute ? 1 : 0,
-                               path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
-               this.path = trimmedPath.split(separator);
-               for (int i = 0; i < this.path.length; i++) {
-                       this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
-               }
-               this.hashCode = this.path[this.path.length - 1].hashCode();
-               assert !(absolute && fileStore == null);
+               super(filesSystem, path);
+//             this.fs = filesSystem;
+//             if (path == null)
+//                     throw new JcrFsException("Path cannot be null");
+//             if (path.equals(separator)) {// root
+//                     this.path = null;
+//                     this.absolute = true;
+//                     this.hashCode = 0;
+//                     this.fileStore = fs.getBaseFileStore();
+//                     return;
+//             } else if (path.equals("")) {// empty path
+//                     this.path = new String[] { "" };
+//                     this.absolute = false;
+//                     this.fileStore = null;
+//                     this.hashCode = "".hashCode();
+//                     return;
+//             }
+//
+//             if (path.equals("~")) {// home
+//                     path = filesSystem.getUserHomePath();
+//                     if (path == null)
+//                             throw new JcrFsException("No home directory available");
+//             }
+//
+//             this.absolute = path.charAt(0) == separatorChar ? true : false;
+//
+//             this.fileStore = absolute ? fs.getFileStore(path) : null;
+//
+//             String trimmedPath = path.substring(absolute ? 1 : 0,
+//                             path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
+//             this.path = trimmedPath.split(separator);
+//             for (int i = 0; i < this.path.length; i++) {
+//                     this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
+//             }
+//             this.hashCode = this.path[this.path.length - 1].hashCode();
+//             assert !(absolute && fileStore == null);
        }
 
        public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
@@ -76,53 +66,69 @@ public class JcrPath implements Path {
 
        /** Internal optimisation */
        private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
-               this.fs = filesSystem;
-               this.path = path;
-               this.absolute = path == null ? true : absolute;
-               if (this.absolute && fileStore == null)
-                       throw new IllegalArgumentException("Absolute path requires a file store");
-               if (!this.absolute && fileStore != null)
-                       throw new IllegalArgumentException("A file store should not be provided for a relative path");
-               this.fileStore = fileStore;
-               this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
-               assert !(absolute && fileStore == null);
+               super(filesSystem, fileStore, path, absolute);
+//             this.fs = filesSystem;
+//             this.path = path;
+//             this.absolute = path == null ? true : absolute;
+//             if (this.absolute && fileStore == null)
+//                     throw new IllegalArgumentException("Absolute path requires a file store");
+//             if (!this.absolute && fileStore != null)
+//                     throw new IllegalArgumentException("A file store should not be provided for a relative path");
+//             this.fileStore = fileStore;
+//             this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
+//             assert !(absolute && fileStore == null);
        }
 
-       @Override
-       public FileSystem getFileSystem() {
-               return fs;
+       protected String cleanUpSegment(String segment) {
+               return Text.unescapeIllegalJcrChars(segment);
        }
 
        @Override
-       public boolean isAbsolute() {
-               return absolute;
+       protected JcrPath newInstance(String path) {
+               return new JcrPath(getFileSystem(), path);
        }
 
        @Override
-       public Path getRoot() {
-               if (path == null)
-                       return this;
-               return new JcrPath(fs, separator);
-       }
+       protected JcrPath newInstance(String[] segments, boolean absolute) {
+               return new JcrPath(getFileSystem(), getFileStore(), segments, absolute);
 
-       @Override
-       public String toString() {
-               return toFsPath(path);
        }
 
-       private String toFsPath(String[] path) {
-               if (path == null)
-                       return "/";
-               StringBuilder sb = new StringBuilder();
-               if (isAbsolute())
-                       sb.append('/');
-               for (int i = 0; i < path.length; i++) {
-                       if (i != 0)
-                               sb.append('/');
-                       sb.append(path[i]);
-               }
-               return sb.toString();
-       }
+//     @Override
+//     public FileSystem getFileSystem() {
+//             return fs;
+//     }
+//
+//     @Override
+//     public boolean isAbsolute() {
+//             return absolute;
+//     }
+//
+//     @Override
+//     public Path getRoot() {
+//             if (path == null)
+//                     return this;
+//             return new JcrPath(fs, separator);
+//     }
+//
+//     @Override
+//     public String toString() {
+//             return toFsPath(path);
+//     }
+//
+//     private String toFsPath(String[] path) {
+//             if (path == null)
+//                     return "/";
+//             StringBuilder sb = new StringBuilder();
+//             if (isAbsolute())
+//                     sb.append('/');
+//             for (int i = 0; i < path.length; i++) {
+//                     if (i != 0)
+//                             sb.append('/');
+//                     sb.append(path[i]);
+//             }
+//             return sb.toString();
+//     }
 
 //     @Deprecated
 //     private String toJcrPath() {
@@ -144,204 +150,187 @@ public class JcrPath implements Path {
 //             return sb.toString();
 //     }
 
-       @Override
-       public Path getFileName() {
-               if (path == null)
-                       return null;
-               return new JcrPath(fs, path[path.length - 1]);
-       }
-
-       @Override
-       public Path getParent() {
-               if (path == null)
-                       return null;
-               if (path.length == 1)// root
-                       return new JcrPath(fs, separator);
-               String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
-               if (!absolute)
-                       return new JcrPath(fs, null, parentPath, absolute);
-               else
-                       return new JcrPath(fs, toFsPath(parentPath));
-       }
-
-       @Override
-       public int getNameCount() {
-               if (path == null)
-                       return 0;
-               return path.length;
-       }
-
-       @Override
-       public Path getName(int index) {
-               if (path == null)
-                       return null;
-               return new JcrPath(fs, path[index]);
-       }
-
-       @Override
-       public Path subpath(int beginIndex, int endIndex) {
-               if (path == null)
-                       return null;
-               String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
-               return new JcrPath(fs, null, parentPath, false);
-       }
-
-       @Override
-       public boolean startsWith(Path other) {
-               return toString().startsWith(other.toString());
-       }
-
-       @Override
-       public boolean startsWith(String other) {
-               return toString().startsWith(other);
-       }
-
-       @Override
-       public boolean endsWith(Path other) {
-               return toString().endsWith(other.toString());
-       }
-
-       @Override
-       public boolean endsWith(String other) {
-               return toString().endsWith(other);
-       }
-
-       @Override
-       public Path normalize() {
-               // always normalized
-               return this;
-       }
-
-       @Override
-       public Path resolve(Path other) {
-               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);
-               }
-               if (!absolute)
-                       return new JcrPath(fs, null, newPath, absolute);
-               else {
-                       return new JcrPath(fs, toFsPath(newPath));
-               }
-       }
-
-       @Override
-       public final Path resolve(String other) {
-               return resolve(getFileSystem().getPath(other));
-       }
-
-       @Override
-       public final Path resolveSibling(Path other) {
-               if (other == null)
-                       throw new NullPointerException();
-               Path parent = getParent();
-               return (parent == null) ? other : parent.resolve(other);
-       }
-
-       @Override
-       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) {
-               if (equals(other))
-                       return new JcrPath(fs, "");
-               if (other.startsWith(this)) {
-                       String p1 = toString();
-                       String p2 = other.toString();
-                       String relative = p2.substring(p1.length(), p2.length());
-                       if (relative.charAt(0) == '/')
-                               relative = relative.substring(1);
-                       return new JcrPath(fs, relative);
-               }
-               throw new IllegalArgumentException(other + " cannot be relativized against " + this);
-       }
-
-       @Override
-       public URI toUri() {
-               try {
-                       return new URI(fs.provider().getScheme(), toString(), null);
-               } catch (URISyntaxException e) {
-                       throw new JcrFsException("Cannot create URI for " + toString(), e);
-               }
-       }
-
-       @Override
-       public Path toAbsolutePath() {
-               if (isAbsolute())
-                       return this;
-               return new JcrPath(fs, fileStore, path, true);
-       }
-
-       @Override
-       public Path toRealPath(LinkOption... options) throws IOException {
-               return this;
-       }
-
-       @Override
-       public File toFile() {
-               throw new UnsupportedOperationException();
-       }
+//     @Override
+//     public Path getFileName() {
+//             if (path == null)
+//                     return null;
+//             return new JcrPath(fs, path[path.length - 1]);
+//     }
+//
+//     @Override
+//     public Path getParent() {
+//             if (path == null)
+//                     return null;
+//             if (path.length == 1)// root
+//                     return new JcrPath(fs, separator);
+//             String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
+//             if (!absolute)
+//                     return new JcrPath(fs, null, parentPath, absolute);
+//             else
+//                     return new JcrPath(fs, toFsPath(parentPath));
+//     }
+//
+//     @Override
+//     public int getNameCount() {
+//             if (path == null)
+//                     return 0;
+//             return path.length;
+//     }
+//
+//     @Override
+//     public Path getName(int index) {
+//             if (path == null)
+//                     return null;
+//             return new JcrPath(fs, path[index]);
+//     }
+//
+//     @Override
+//     public Path subpath(int beginIndex, int endIndex) {
+//             if (path == null)
+//                     return null;
+//             String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
+//             return new JcrPath(fs, null, parentPath, false);
+//     }
+//
+//     @Override
+//     public boolean startsWith(Path other) {
+//             return toString().startsWith(other.toString());
+//     }
+//
+//     @Override
+//     public boolean startsWith(String other) {
+//             return toString().startsWith(other);
+//     }
+//
+//     @Override
+//     public boolean endsWith(Path other) {
+//             return toString().endsWith(other.toString());
+//     }
+//
+//     @Override
+//     public boolean endsWith(String other) {
+//             return toString().endsWith(other);
+//     }
 
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
+//     @Override
+//     public Path normalize() {
+//             // always normalized
+//             return this;
+//     }
 
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
+//     @Override
+//     public Path resolve(Path other) {
+//             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);
+//             }
+//             if (!absolute)
+//                     return new JcrPath(fs, null, newPath, absolute);
+//             else {
+//                     return new JcrPath(fs, toFsPath(newPath));
+//             }
+//     }
+//
+//     @Override
+//     public final Path resolve(String other) {
+//             return resolve(getFileSystem().getPath(other));
+//     }
+//
+//     @Override
+//     public final Path resolveSibling(Path other) {
+//             if (other == null)
+//                     throw new NullPointerException();
+//             Path parent = getParent();
+//             return (parent == null) ? other : parent.resolve(other);
+//     }
+//
+//     @Override
+//     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) {
+//             if (equals(other))
+//                     return new JcrPath(fs, "");
+//             if (other.startsWith(this)) {
+//                     String p1 = toString();
+//                     String p2 = other.toString();
+//                     String relative = p2.substring(p1.length(), p2.length());
+//                     if (relative.charAt(0) == '/')
+//                             relative = relative.substring(1);
+//                     return new JcrPath(fs, relative);
+//             }
+//             throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+//     }
 
-       @Override
-       public int compareTo(Path other) {
-               return toString().compareTo(other.toString());
-       }
+//     @Override
+//     public URI toUri() {
+//             try {
+//                     return new URI(fs.provider().getScheme(), toString(), null);
+//             } catch (URISyntaxException e) {
+//                     throw new JcrFsException("Cannot create URI for " + toString(), e);
+//             }
+//     }
+//
+//     @Override
+//     public Path toAbsolutePath() {
+//             if (isAbsolute())
+//                     return this;
+//             return new JcrPath(fs, fileStore, path, true);
+//     }
+//
+//     @Override
+//     public Path toRealPath(LinkOption... options) throws IOException {
+//             return this;
+//     }
+//
+//     @Override
+//     public File toFile() {
+//             throw new UnsupportedOperationException();
+//     }
 
        public Node getNode() throws RepositoryException {
                if (!isAbsolute())// TODO default dir
                        throw new JcrFsException("Cannot get a JCR node from a relative path");
-               assert fileStore != null;
-               return fileStore.toNode(path);
+               assert getFileStore() != null;
+               return getFileStore().toNode(getSegments());
 //             String pathStr = toJcrPath();
 //             Session session = fs.getSession();
 //             // TODO synchronize on the session ?
@@ -349,45 +338,47 @@ public class JcrPath implements Path {
 //                     return null;
 //             return session.getNode(pathStr);
        }
+//
+//     @Override
+//     public boolean equals(Object obj) {
+//             if (!(obj instanceof JcrPath))
+//                     return false;
+//             JcrPath other = (JcrPath) obj;
+//
+//             if (path == null) {// root
+//                     if (other.path == null)// root
+//                             return true;
+//                     else
+//                             return false;
+//             } else {
+//                     if (other.path == null)// root
+//                             return false;
+//             }
+//             // non root
+//             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 boolean equals(Object obj) {
-               if (!(obj instanceof JcrPath))
-                       return false;
-               JcrPath other = (JcrPath) obj;
-
-               if (path == null) {// root
-                       if (other.path == null)// root
-                               return true;
-                       else
-                               return false;
-               } else {
-                       if (other.path == null)// root
-                               return false;
-               }
-               // non root
-               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
+//     public int hashCode() {
+//             return hashCode;
+//     }
 
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               return new JcrPath(fs, toString());
-       }
+//     @Override
+//     protected Object clone() throws CloneNotSupportedException {
+//             return new JcrPath(fs, toString());
+//     }
 
-       @Override
-       protected void finalize() throws Throwable {
-               Arrays.fill(path, null);
-       }
+//     @Override
+//     protected void finalize() throws Throwable {
+//             Arrays.fill(path, null);
+//     }
 
+       
+       
 }
index 6d9d05c2a4b89da4caaa4b51a8b27d13972302fc..e8f24c9de8b44ebe8ba68500c314469ff52593f5 100644 (file)
@@ -11,10 +11,11 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Workspace;
 
+import org.argeo.api.gcr.fs.AbstractFsStore;
 import org.argeo.jcr.JcrUtils;
 
 /** A {@link FileStore} implementation based on JCR {@link Workspace}. */
-public class WorkspaceFileStore extends FileStore {
+public class WorkspaceFileStore extends AbstractFsStore {
        private final String mountPath;
        private final Workspace workspace;
        private final String workspaceName;
diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java
new file mode 100644 (file)
index 0000000..354be53
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.cms.swt.gcr;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.cms.gcr.fs.FsContentSession;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class GcrContentTreeView extends Composite {
+       private Tree tree;
+       private Table table;
+       private Content rootContent;
+
+       private Content selected;
+
+       public GcrContentTreeView(Composite parent, int style, Content content) {
+               super(parent, style);
+               this.rootContent = content;
+               this.selected = rootContent;
+               setLayout(new GridLayout(2, false));
+               initTree();
+               GridData treeGd = CmsSwtUtils.fillHeight();
+               treeGd.widthHint = 300;
+               tree.setLayoutData(treeGd);
+               initTable();
+               
+               table.setLayoutData(CmsSwtUtils.fillAll());
+       }
+
+       protected void initTree() {
+               tree = new Tree(this, 0);
+               for (Content c : rootContent) {
+                       TreeItem root = new TreeItem(tree, 0);
+                       root.setText(c.getName());
+                       root.setData(c);
+                       new TreeItem(root, 0);
+               }
+               tree.addListener(SWT.Expand, event -> {
+                       final TreeItem root = (TreeItem) event.item;
+                       TreeItem[] items = root.getItems();
+                       for (TreeItem item : items) {
+                               if (item.getData() != null)
+                                       return;
+                               item.dispose();
+                       }
+                       Content content = (Content) root.getData();
+                       for (Content c : content) {
+                               TreeItem item = new TreeItem(root, 0);
+                               item.setText(c.getName());
+                               item.setData(c);
+                               boolean hasChildren = true;
+                               if (hasChildren) {
+                                       new TreeItem(item, 0);
+                               }
+                       }
+               });
+               tree.addListener(SWT.Selection, event -> {
+                       TreeItem item = (TreeItem) event.item;
+                       selected = (Content) item.getData();
+                       refreshTable();
+               });
+       }
+
+       protected void initTable() {
+               table = new Table(this, 0);
+               table.setLinesVisible(true);
+               table.setHeaderVisible(true);
+               TableColumn keyCol = new TableColumn(table, SWT.NONE);
+               keyCol.setText("Attribute");
+               keyCol.setWidth(200);
+               TableColumn valueCol = new TableColumn(table, SWT.NONE);
+               valueCol.setText("Value");
+               keyCol.setWidth(300);
+               refreshTable();
+       }
+
+       protected void refreshTable() {
+               for (TableItem item : table.getItems()) {
+                       item.dispose();
+               }
+               for (String key : selected.keySet()) {
+                       TableItem item = new TableItem(table, 0);
+                       item.setText(0, key);
+                       Object value = selected.get(key);
+                       item.setText(1, value.toString());
+               }
+               table.getColumn(0).pack();
+               table.getColumn(1).pack();
+       }
+
+       public static void main(String[] args) {
+               Path basePath;
+               if (args.length > 0) {
+                       basePath = Paths.get(args[0]);
+               } else {
+                       basePath = Paths.get(System.getProperty("user.home"));
+               }
+
+               final Display display = new Display();
+               final Shell shell = new Shell(display);
+               shell.setText(basePath.toString());
+               shell.setLayout(new FillLayout());
+
+               FsContentSession contentSession = new FsContentSession(basePath);
+               GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get());
+
+               shell.setSize(shell.computeSize(800, 600));
+               shell.open();
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+               display.dispose();
+       }
+}
diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java
new file mode 100644 (file)
index 0000000..63b29c9
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.cms.swt.gcr;
+
+import org.argeo.api.cms.MvcProvider;
+import org.argeo.api.gcr.Content;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+@FunctionalInterface
+public interface SwtUiProvider extends MvcProvider<Composite, Content, Control> {
+
+}
index 2766d0e3eaaf81ebff14fa5e28e4741ddde53de2..bb131f5aac146ca0ac2dfd24847d6e9a5420b283 100644 (file)
@@ -13,10 +13,10 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
-import org.argeo.api.gcr.AbstractContent;
 import org.argeo.api.gcr.Content;
 import org.argeo.api.gcr.ContentResourceException;
-import org.argeo.api.gcr.ContentSession;
+import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.spi.AbstractContent;
 
 public class FsContent extends AbstractContent implements Content {
        private static final Set<String> BASIC_KEYS = new HashSet<>(
@@ -83,10 +83,6 @@ public class FsContent extends AbstractContent implements Content {
                return (A) value;
        }
 
-       @Override
-       public ContentSession getSession() {
-               return contentSession;
-       }
 
        @Override
        protected Iterable<String> keys() {
index 68cea4f93a4b2231aa258c897cc35993270f83a2..75ec1917219fe31d909795aeb64a45ff2a3266bc 100644 (file)
@@ -6,10 +6,10 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 
 import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSession;
-import org.argeo.api.gcr.Contents;
+import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.ContentUtils;
 
-public class FsContentSession implements ContentSession {
+public class FsContentSession implements ContentSystemProvider {
        private final Path rootPath;
 
        public FsContentSession(Path rootPath) {
@@ -26,7 +26,7 @@ public class FsContentSession implements ContentSession {
                Path path = Paths.get("/home/mbaudier/tmp");
                System.out.println(FileSystems.getDefault().supportedFileAttributeViews());
                FsContentSession contentSession = new FsContentSession(path);
-               Contents.traverse(contentSession.get(), (c, d) -> Contents.print(c, System.out, d, true));
+               ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
 
        }
 }
index 19bae2d87797045a4c6a88200a3a2a34f3ae32d0..b42e0cf0dd8c6f76f33c424c47b0ea996082e9a9 100644 (file)
@@ -4,9 +4,9 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
-import org.argeo.api.gcr.AbstractContent;
 import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSession;
+import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.spi.AbstractContent;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
@@ -63,10 +63,6 @@ public class DomContent extends AbstractContent implements Content {
                        return null;
        }
 
-       @Override
-       public ContentSession getSession() {
-               return contentSession;
-       }
 
        @Override
        public boolean hasText() {
index ededa6cca8e7884c100a6ce2e74720afec6a7e4b..1f8c0e90c16e5ee5170548bd99f1a163a3c9c6b1 100644 (file)
@@ -11,11 +11,11 @@ import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSession;
-import org.argeo.api.gcr.Contents;
+import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.ContentUtils;
 import org.w3c.dom.Document;
 
-public class DomContentSession implements ContentSession {
+public class DomContentSession implements ContentSystemProvider {
        private Document document;
 
        public DomContentSession(Document document) {
@@ -44,7 +44,7 @@ public class DomContentSession implements ContentSession {
                Document doc = dBuilder.parse(Files.newInputStream(testFile));
 
                DomContentSession contentSession = new DomContentSession(doc);
-               Contents.traverse(contentSession.get(), (c, d) -> Contents.print(c, System.out, d, true));
+               ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
 
        }
 }