Introduce CRUD to GCR
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 18 Jan 2022 09:21:19 +0000 (10:21 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 18 Jan 2022 09:21:19 +0000 (10:21 +0100)
26 files changed:
org.argeo.api/src/org/argeo/api/gcr/Content.java
org.argeo.api/src/org/argeo/api/gcr/ContentName.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentSession.java
org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/CrName.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java
org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java
org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.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/FsContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java
org.argeo.util/src/org/argeo/util/BasicSyncFileVisitor.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/FsUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/SyncResult.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/NodeOID.java

index b2fc3c9063a37f156135f5efb078e7a7eb18a850..9ce6ea4f6b3f70f4add3ac5895c5e603523622de 100644 (file)
@@ -9,11 +9,22 @@ public interface Content extends Iterable<Content>, Map<String, Object> {
 
        String getName();
 
-//     Iterable<String> keys();
+       String getPath();
+
+       Content getParent();
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
 
        <A> A get(String key, Class<A> clss) throws IllegalArgumentException;
 
-//     ContentSession getSession();
+       /*
+        * CONTENT OPERATIONS
+        */
+       Content add(String name, ContentName... classes);
+
+       void remove();
 
        /*
         * DEFAULT METHODS
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentName.java b/org.argeo.api/src/org/argeo/api/gcr/ContentName.java
new file mode 100644 (file)
index 0000000..ddc80c5
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.api.gcr;
+
+import java.util.UUID;
+
+public interface ContentName {
+       UUID getUuid();
+
+       ContentNamespace getNamespace();
+
+       String getName();
+
+       static boolean contains(ContentName[] classes, ContentName name) {
+               for (ContentName clss : classes) {
+                       if (clss.getUuid().equals(name.getUuid()))
+                               return true;
+               }
+               return false;
+       }
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java b/org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java
new file mode 100644 (file)
index 0000000..a595571
--- /dev/null
@@ -0,0 +1,106 @@
+package org.argeo.api.gcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * An XML-style namespace with a related UUID v3.
+ * 
+ * @see https://www.w3.org/TR/xml-names/
+ */
+public class ContentNamespace {
+       private final UUID uuid;
+       private final URI uri;
+
+       public ContentNamespace(String uri) {
+               try {
+                       this.uri = new URI(uri);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot interpret " + uri + " as URI", e);
+               }
+               this.uuid = namespaceUuid(this.uri);
+       }
+
+       public ContentNamespace(URI uri) {
+               this.uri = uri;
+               this.uuid = namespaceUuid(uri);
+       }
+
+       ContentNamespace(URI uri, UUID uuid) {
+               this.uri = uri;
+               assert uuid.equals(namespaceUuid(uri));
+               this.uuid = uuid;
+       }
+
+       /** Empty namespace */
+       private ContentNamespace() {
+               try {
+                       this.uri = new URI("");
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot create empty URI");
+               }
+               this.uuid = NIL_UUID;
+       }
+
+       public UUID getUuid() {
+               return uuid;
+       }
+
+       public URI getUri() {
+               return uri;
+       }
+
+       public UUID nameUuid(String name) {
+               return nameUuid(getUuid(), name);
+       }
+
+
+       public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+       public final static ContentNamespace EMPTY_NS = new ContentNamespace();
+
+       /**
+        * The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS
+        * namespace, to be used as a base for the namespaces.
+        * 
+        * @see https://www.w3.org/TR/xml-names/#ns-decl
+        */
+       // uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/
+       // NOTE : must be declared before default namespaces
+       public final static UUID XMLNS_UUID = UUID.fromString("4b352aad-ba1c-3139-b9d3-41e5816f6088");
+       public final static ContentNamespace CR_NS = new ContentNamespace("http://argeo.org/ns/cr");
+
+
+       public static UUID namespaceUuid(URI namespaceUri) {
+               Objects.requireNonNull(namespaceUri, "Namespace URI cannot be null");
+               return nameUUIDv3(XMLNS_UUID, namespaceUri.toString().getBytes(StandardCharsets.UTF_8));
+       }
+
+       public static UUID nameUuid(UUID namespace, String name) {
+               Objects.requireNonNull(namespace, "Namespace cannot be null");
+               Objects.requireNonNull(namespace, "Name cannot be null");
+               return nameUUIDv3(namespace, name.getBytes(StandardCharsets.UTF_8));
+       }
+
+       /*
+        * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+        */
+       private static UUID nameUUIDv3(UUID namespace, byte[] name) {
+               byte[] arr = new byte[name.length + 16];
+               copyUuidBytes(namespace, arr, 0);
+               System.arraycopy(name, 0, arr, 16, name.length);
+               return UUID.nameUUIDFromBytes(arr);
+       }
+
+       private static void copyUuidBytes(UUID uuid, byte[] arr, int offset) {
+               long msb = uuid.getMostSignificantBits();
+               long lsb = uuid.getLeastSignificantBits();
+               assert arr.length >= 16 + offset;
+               for (int i = offset; i < 8 + offset; i++)
+                       arr[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
+               for (int i = 8 + offset; i < 16 + offset; i++)
+                       arr[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
+       }
+}
index b7ffcd0431d0f9d6c5a8f8af3ba7160376d1a13c..d779093be863c160914e32a22d5fcca415875431 100644 (file)
@@ -8,4 +8,6 @@ public interface ContentSession {
        Subject getSubject();
 
        Locale getLocale();
+
+       Content get(String path);
 }
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java
deleted file mode 100644 (file)
index 04ac11a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-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
deleted file mode 100644 (file)
index 4f0d66d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-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/CrName.java b/org.argeo.api/src/org/argeo/api/gcr/CrName.java
new file mode 100644 (file)
index 0000000..3fc1481
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.api.gcr;
+
+import java.util.UUID;
+
+/** Standard names. */
+public enum CrName implements ContentName {
+       /*
+        * TYPES
+        */
+       COLLECTION("collection"), // a collection type
+
+       /*
+        * ATTRIBUTES
+        */
+       UUID("uuid"), // the UUID of a content
+       //
+       ;
+
+       private String name;
+       private UUID uuid;
+
+       CrName(String name) {
+               this.name = name;
+               this.uuid = ContentNamespace.CR_NS.nameUuid(name);
+       }
+
+       @Override
+       public UUID getUuid() {
+               return uuid;
+       }
+
+       @Override
+       public ContentNamespace getNamespace() {
+               return ContentNamespace.CR_NS;
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+}
index 2da186ef2538eb6d2d1f2da67be1d9dae6675d6a..79c59f2f66ad06c0b1cf2ca74b0b6917f1610b9a 100644 (file)
@@ -1,7 +1,11 @@
 package org.argeo.api.gcr.spi;
 
 import java.util.AbstractMap;
-import java.util.HashSet;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.argeo.api.gcr.Content;
@@ -10,39 +14,117 @@ public abstract class AbstractContent extends AbstractMap<String, Object> implem
 
        @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>() {
+//             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() {
+//                                     return get(key, Object.class);
+//                             }
+//
+//                             @Override
+//                             public Object setValue(Object value) {
+//                                     throw new UnsupportedOperationException();
+//                             }
+//
+//                     };
+//                     result.add(entry);
+//             }
+               Set<Entry<String, Object>> result = new AttrSet();
+               return result;
+       }
+
+       protected abstract Iterable<String> keys();
+
+       protected abstract void removeAttr(String key);
+
+       @Override
+       public String getPath() {
+               List<Content> ancestors = new ArrayList<>();
+               collectAncestors(ancestors, this);
+               StringBuilder path = new StringBuilder();
+               for (Content c : ancestors) {
+                       String name = c.getName();
+                       if (!"".equals(name))
+                               path.append('/').append(name);
+               }
+               return path.toString();
+       }
+
+       private void collectAncestors(List<Content> ancestors, Content content) {
+               if (content == null)
+                       return;
+               ancestors.add(0, content);
+               collectAncestors(ancestors, content.getParent());
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+               // check whether clss is Object.class
+               return clss.isAssignableFrom(Object.class);
+       }
+
+       @Override
+       public String toString() {
+               return "content "+getPath();
+       }
+
+       /*
+        * SUB CLASSES
+        */
+
+       class AttrSet extends AbstractSet<Entry<String, Object>> {
+
+               @Override
+               public Iterator<Entry<String, Object>> iterator() {
+                       final Iterator<String> keys = keys().iterator();
+                       Iterator<Entry<String, Object>> it = new Iterator<Map.Entry<String, Object>>() {
+
+                               String key = null;
 
                                @Override
-                               public String getKey() {
-                                       return key;
+                               public boolean hasNext() {
+                                       return keys.hasNext();
                                }
 
                                @Override
-                               public Object getValue() {
+                               public Entry<String, Object> next() {
+                                       key = keys.next();
                                        // TODO check type
-                                       return get(key, Object.class);
+                                       Object value = get(key, Object.class);
+                                       AbstractMap.SimpleEntry<String, Object> entry = new SimpleEntry<>(key, value);
+                                       return entry;
                                }
 
                                @Override
-                               public Object setValue(Object value) {
-                                       throw new UnsupportedOperationException();
+                               public void remove() {
+                                       if (key != null) {
+                                               AbstractContent.this.removeAttr(key);
+                                       } else {
+                                               throw new IllegalStateException("Iteration has not started");
+                                       }
                                }
 
                        };
-                       result.add(entry);
+                       return it;
+               }
+
+               @Override
+               public int size() {
+                       int count = 0;
+                       for (String key : keys()) {
+                               count++;
+                       }
+                       return count;
                }
-               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.api/src/org/argeo/api/gcr/spi/ContentProvider.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java
new file mode 100644 (file)
index 0000000..9731319
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.api.gcr.spi;
+
+import java.util.function.Supplier;
+
+import org.argeo.api.gcr.Content;
+
+public interface ContentProvider extends Supplier<Content> {
+
+       Content get(String relativePath);
+
+}
index b136a0105eab82f78fa5d8c269bea4807205a305..d319c5504e4f32f4ecbd7d1b2313a5f94f118d73 100644 (file)
@@ -10,18 +10,20 @@ import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
 
 import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentName;
 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 JcrContentProvider contentProvider;
        private Node jcrNode;
 
-       protected JcrContent(JcrContentSession contentSession, Node node) {
-               this.contentSession = contentSession;
+       protected JcrContent(JcrContentProvider contentSession, Node node) {
+               this.contentProvider = contentSession;
                this.jcrNode = node;
        }
 
@@ -41,7 +43,7 @@ public class JcrContent extends AbstractContent {
        @Override
        public Iterator<Content> iterator() {
                try {
-                       return new JcrContentIterator(contentSession, jcrNode.getNodes());
+                       return new JcrContentIterator(contentProvider, jcrNode.getNodes());
                } catch (RepositoryException e) {
                        throw new JcrException("Cannot list children of " + jcrNode, e);
                }
@@ -55,7 +57,7 @@ public class JcrContent extends AbstractContent {
                        public Iterator<String> iterator() {
                                try {
                                        PropertyIterator propertyIterator = jcrNode.getProperties();
-                                       return new JcrKeyIterator(contentSession, propertyIterator);
+                                       return new JcrKeyIterator(contentProvider, propertyIterator);
                                } catch (RepositoryException e) {
                                        throw new JcrException("Cannot retrive properties from " + jcrNode, e);
                                }
@@ -94,12 +96,12 @@ public class JcrContent extends AbstractContent {
        }
 
        static class JcrContentIterator implements Iterator<Content> {
-               private final JcrContentSession contentSession;
+               private final JcrContentProvider 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) {
+               protected JcrContentIterator(JcrContentProvider contentSession, NodeIterator nodeIterator) {
                        this.contentSession = contentSession;
                        this.nodeIterator = nodeIterator;
                }
@@ -118,18 +120,59 @@ public class JcrContent extends AbstractContent {
                @Override
                public void remove() {
                        if (current != null) {
-                               // current.getJcrNode().remove();
+                               Jcr.remove(current.getJcrNode());
+                       }
+               }
+
+       }
+
+       @Override
+       public Content getParent() {
+               return new JcrContent(contentProvider, Jcr.getParent(getJcrNode()));
+       }
+
+       @Override
+       public Content add(String name, ContentName... classes) {
+               if (classes.length > 0) {
+                       ContentName primaryType = classes[0];
+                       Node child = Jcr.addNode(getJcrNode(), name, primaryType.toString());
+                       for (int i = 1; i < classes.length; i++) {
+                               try {
+                                       child.addMixin(classes[i].toString());
+                               } catch (RepositoryException e) {
+                                       throw new JcrException("Cannot add child to " + getJcrNode(), e);
+                               }
+                       }
+
+               } else {
+                       Jcr.addNode(getJcrNode(), name, NodeType.NT_UNSTRUCTURED);
+               }
+               return null;
+       }
+
+       @Override
+       public void remove() {
+               Jcr.remove(getJcrNode());
+       }
+
+       @Override
+       protected void removeAttr(String key) {
+               Property property = Jcr.getProperty(getJcrNode(), key);
+               if (property != null) {
+                       try {
+                               property.remove();
+                       } catch (RepositoryException e) {
+                               throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
                        }
-                       throw new UnsupportedOperationException();
                }
 
        }
 
        static class JcrKeyIterator implements Iterator<String> {
-               private final JcrContentSession contentSession;
+               private final JcrContentProvider contentSession;
                private final PropertyIterator propertyIterator;
 
-               protected JcrKeyIterator(JcrContentSession contentSession, PropertyIterator propertyIterator) {
+               protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
                        this.contentSession = contentSession;
                        this.propertyIterator = propertyIterator;
                }
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java
new file mode 100644 (file)
index 0000000..5e4a1b3
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.jcr.gcr;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.cms.jcr.CmsJcrUtils;
+
+public class JcrContentProvider implements ContentProvider {
+       private Repository jcrRepository;
+       private Session jcrSession;
+
+       public void init() {
+               jcrSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
+       }
+
+       public void setJcrRepository(Repository jcrRepository) {
+               this.jcrRepository = jcrRepository;
+       }
+
+       @Override
+       public Content get() {
+               return null;
+       }
+
+       @Override
+       public Content get(String relativePath) {
+               // TODO Auto-generated method stub
+               return 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
deleted file mode 100644 (file)
index 057ed30..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644 (file)
index 98ecbc8..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-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 354be53532261a9d80a1b71a0ec13a1ad0b0cd2c..092a35739a6fb9c2c630a4501195273c54def0e6 100644 (file)
@@ -4,7 +4,7 @@ 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.gcr.fs.FsContentProvider;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.FillLayout;
@@ -114,7 +114,7 @@ public class GcrContentTreeView extends Composite {
                shell.setText(basePath.toString());
                shell.setLayout(new FillLayout());
 
-               FsContentSession contentSession = new FsContentSession(basePath);
+               FsContentProvider contentSession = new FsContentProvider(basePath);
                GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get());
 
                shell.setSize(shell.computeSize(800, 600));
diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java
new file mode 100644 (file)
index 0000000..0baae35
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.cms.gcr;
+
+import java.security.AccessController;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentRepository;
+import org.argeo.api.gcr.ContentSession;
+import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+
+public class CmsContentRepository implements ContentRepository {
+       private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+
+       @Override
+       public ContentSession get() {
+               return get(CmsContextImpl.getCmsContext().getDefaultLocale());
+       }
+
+       @Override
+       public ContentSession get(Locale locale) {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               return new CmsContentSession(subject, locale);
+       }
+
+       public void addProvider(String base, ContentProvider provider) {
+               partitions.put(base, provider);
+       }
+
+       class CmsContentSession implements ContentSession {
+               private Subject subject;
+               private Locale locale;
+
+               public CmsContentSession(Subject subject, Locale locale) {
+                       this.subject = subject;
+                       this.locale = locale;
+               }
+
+               @Override
+               public Content get(String path) {
+                       Map.Entry<String, ContentProvider> provider = partitions.floorEntry(path);
+                       String relativePath = path.substring(provider.getKey().length());
+                       return provider.getValue().get(relativePath);
+               }
+
+               @Override
+               public Subject getSubject() {
+                       return subject;
+               }
+
+               @Override
+               public Locale getLocale() {
+                       return locale;
+               }
+
+       }
+
+}
index bb131f5aac146ca0ac2dfd24847d6e9a5420b283..62ae613f0f737b4c71befa8bee527daf1f59e9d9 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.cms.gcr.fs;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -15,10 +16,14 @@ import java.util.Set;
 
 import org.argeo.api.gcr.Content;
 import org.argeo.api.gcr.ContentResourceException;
-import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.CrName;
+import org.argeo.api.gcr.ContentName;
 import org.argeo.api.gcr.spi.AbstractContent;
+import org.argeo.util.FsUtils;
 
 public class FsContent extends AbstractContent implements Content {
+       private final static String USER_ = "user:";
+
        private static final Set<String> BASIC_KEYS = new HashSet<>(
                        Arrays.asList("basic:creationTime", "basic:lastModifiedTime", "basic:size", "basic:fileKey"));
        private static final Set<String> POSIX_KEYS;
@@ -29,42 +34,38 @@ public class FsContent extends AbstractContent implements Content {
                POSIX_KEYS.add("posix:permissions");
        }
 
-       private FsContentSession contentSession;
+       private final FsContentProvider contentProvider;
        private final Path path;
+       private final boolean isRoot;
 
-       public FsContent(FsContentSession contentSession, Path path) {
+       public FsContent(FsContentProvider contentProvider, Path path) {
                super();
-               this.contentSession = contentSession;
+               this.contentProvider = contentProvider;
                this.path = path;
+               this.isRoot = contentProvider.isRoot(path);
        }
 
        private boolean isPosix() {
                return path.getFileSystem().supportedFileAttributeViews().contains("posix");
        }
 
-       @Override
-       public Iterator<Content> iterator() {
-               if (Files.isDirectory(path)) {
-                       try {
-                               return Files.list(path).map((p) -> (Content) new FsContent(contentSession, p)).iterator();
-                       } catch (IOException e) {
-                               throw new ContentResourceException("Cannot list " + path, e);
-                       }
-               } else {
-                       return Collections.emptyIterator();
-               }
-       }
-
        @Override
        public String getName() {
+               if (isRoot)
+                       return "";
                return path.getFileName().toString();
        }
 
+       /*
+        * ATTRIBUTES
+        */
+
        @Override
        public <A> A get(String key, Class<A> clss) {
                Object value;
                try {
-                       value = Files.getAttribute(path, key);
+                       // We need to add user: when accessing via Files#getAttribute
+                       value = Files.getAttribute(path, toFsAttributeKey(key));
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
                }
@@ -83,7 +84,6 @@ public class FsContent extends AbstractContent implements Content {
                return (A) value;
        }
 
-
        @Override
        protected Iterable<String> keys() {
                Set<String> result = new HashSet<>(isPosix() ? POSIX_KEYS : BASIC_KEYS);
@@ -91,13 +91,89 @@ public class FsContent extends AbstractContent implements Content {
                if (udfav != null) {
                        try {
                                for (String name : udfav.list()) {
-                                       result.add("user:" + name);
+                                       result.add(name);
                                }
                        } catch (IOException e) {
-                               throw new ContentResourceException("Cannot liast attributes for " + path, e);
+                               throw new ContentResourceException("Cannot list attributes for " + path, e);
                        }
                }
                return result;
        }
 
+       @Override
+       protected void removeAttr(String key) {
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               try {
+                       udfav.delete(key);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+               }
+       }
+
+       @Override
+       public Object put(String key, Object value) {
+               Object previous = get(key);
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+               try {
+                       int size = udfav.write(key, bb);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+               }
+               return previous;
+       }
+
+       protected String toFsAttributeKey(String key) {
+               if (POSIX_KEYS.contains(key))
+                       return key;
+               else
+                       return USER_ + key;
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+       @Override
+       public Iterator<Content> iterator() {
+               if (Files.isDirectory(path)) {
+                       try {
+                               return Files.list(path).map((p) -> (Content) new FsContent(contentProvider, p)).iterator();
+                       } catch (IOException e) {
+                               throw new ContentResourceException("Cannot list " + path, e);
+                       }
+               } else {
+                       return Collections.emptyIterator();
+               }
+       }
+
+       @Override
+       public Content add(String name, ContentName... classes) {
+               try {
+                       Path newPath = path.resolve(name);
+                       if (ContentName.contains(classes, CrName.COLLECTION))
+                               Files.createDirectory(newPath);
+                       else
+                               Files.createFile(newPath);
+
+//             for(ContentClass clss:classes) {
+//                     Files.setAttribute(newPath, name, newPath, null)
+//             }
+                       return new FsContent(contentProvider, newPath);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot create new content", e);
+               }
+       }
+
+       @Override
+       public void remove() {
+               FsUtils.delete(path);
+       }
+
+       @Override
+       public Content getParent() {
+               if (isRoot)
+                       return null;// TODO deal with mounts
+               return new FsContent(contentProvider, path.getParent());
+       }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java
new file mode 100644 (file)
index 0000000..d15f9c3
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.cms.gcr.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentResourceException;
+import org.argeo.api.gcr.spi.ContentProvider;
+
+public class FsContentProvider implements ContentProvider {
+       private final Path rootPath;
+
+       public FsContentProvider(Path rootPath) {
+               super();
+               this.rootPath = rootPath;
+       }
+
+       boolean isRoot(Path path) {
+               try {
+                       return Files.isSameFile(rootPath, path);
+               } catch (IOException e) {
+                       throw new ContentResourceException(e);
+               }
+       }
+
+       @Override
+       public Content get() {
+               return new FsContent(this, rootPath);
+       }
+
+       @Override
+       public Content get(String relativePath) {
+               return new FsContent(this, rootPath.resolve(relativePath));
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java
deleted file mode 100644 (file)
index 75ec191..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms.gcr.fs;
-
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSystemProvider;
-import org.argeo.api.gcr.ContentUtils;
-
-public class FsContentSession implements ContentSystemProvider {
-       private final Path rootPath;
-
-       public FsContentSession(Path rootPath) {
-               super();
-               this.rootPath = rootPath;
-       }
-
-       @Override
-       public Content get() {
-               return new FsContent(this, rootPath);
-       }
-
-       public static void main(String[] args) {
-               Path path = Paths.get("/home/mbaudier/tmp");
-               System.out.println(FileSystems.getDefault().supportedFileAttributeViews());
-               FsContentSession contentSession = new FsContentSession(path);
-               ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
-
-       }
-}
index b42e0cf0dd8c6f76f33c424c47b0ea996082e9a9..30d590df4f48720768ea011cb55a3cf5c50f5452 100644 (file)
@@ -5,7 +5,7 @@ import java.util.Iterator;
 import java.util.Set;
 
 import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.ContentName;
 import org.argeo.api.gcr.spi.AbstractContent;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Element;
@@ -16,21 +16,21 @@ import org.w3c.dom.Text;
 
 public class DomContent extends AbstractContent implements Content {
 
-       private final DomContentSession contentSession;
+       private final DomContentProvider contentProvider;
        private final Element element;
 
 //     private String text = null;
        private Boolean hasText = null;
 
-       public DomContent(DomContentSession contentSession, Element element) {
-               this.contentSession = contentSession;
+       public DomContent(DomContentProvider contentProvider, Element element) {
+               this.contentProvider = contentProvider;
                this.element = element;
        }
 
        @Override
        public Iterator<Content> iterator() {
                NodeList nodeList = element.getChildNodes();
-               return new ElementIterator(contentSession, nodeList);
+               return new ElementIterator(contentProvider, nodeList);
        }
 
        @Override
@@ -63,7 +63,6 @@ public class DomContent extends AbstractContent implements Content {
                        return null;
        }
 
-
        @Override
        public boolean hasText() {
 //             return element instanceof Text;
@@ -77,7 +76,7 @@ public class DomContent extends AbstractContent implements Content {
                nodes: for (int i = 0; i < nodeList.getLength(); i++) {
                        Node node = nodeList.item(i);
                        if (node instanceof Text) {
-                               Text text =(Text) node;
+                               Text text = (Text) node;
                                if (!text.isElementContentWhitespace()) {
                                        hasText = true;
                                        break nodes;
@@ -101,4 +100,34 @@ public class DomContent extends AbstractContent implements Content {
                        return null;
        }
 
+       @Override
+       public Content getParent() {
+               Node parent = element.getParentNode();
+               if (parent == null)
+                       return null;
+               if (!(parent instanceof Element))
+                       throw new IllegalStateException("Parent is not an element");
+               return new DomContent(contentProvider, (Element) parent);
+       }
+
+       @Override
+       public Content add(String name, ContentName... classes) {
+               // TODO consider classes
+               Element child = contentProvider.createElement(name);
+               return new DomContent(contentProvider, child);
+       }
+
+       @Override
+       public void remove() {
+               // TODO make it more robust
+               element.getParentNode().removeChild(element);
+
+       }
+
+       @Override
+       protected void removeAttr(String key) {
+               element.removeAttribute(key);
+
+       }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java
new file mode 100644 (file)
index 0000000..d1a625e
--- /dev/null
@@ -0,0 +1,61 @@
+package org.argeo.cms.gcr.xml;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentUtils;
+import org.argeo.api.gcr.spi.ContentProvider;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class DomContentProvider implements ContentProvider {
+       private Document document;
+
+       public DomContentProvider(Document document) {
+               this.document = document;
+               this.document.normalizeDocument();
+       }
+
+       @Override
+       public Content get() {
+               return new DomContent(this, document.getDocumentElement());
+       }
+
+       public Element createElement(String name) {
+               return document.createElement(name);
+       }
+
+       @Override
+       public Content get(String relativePath) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       public static void main(String args[]) throws Exception {
+               HashMap<String, Object> map = new HashMap<>();
+               map.put(null, "test");
+               System.out.println(map.get(null));
+
+               Set<String> set = new HashSet<>();
+               set.add(null);
+
+               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+               DocumentBuilder dBuilder = factory.newDocumentBuilder();
+               Path testFile;
+               testFile = Paths.get(System.getProperty("user.home") + "/dev/git/unstable/argeo-commons/pom.xml");
+               testFile = Paths.get(System.getProperty("user.home") + "/tmp/test.xml");
+               Document doc = dBuilder.parse(Files.newInputStream(testFile));
+
+               DomContentProvider contentSession = new DomContentProvider(doc);
+               ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java
deleted file mode 100644 (file)
index 1f8c0e9..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.argeo.cms.gcr.xml;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSystemProvider;
-import org.argeo.api.gcr.ContentUtils;
-import org.w3c.dom.Document;
-
-public class DomContentSession implements ContentSystemProvider {
-       private Document document;
-
-       public DomContentSession(Document document) {
-               this.document = document;
-               this.document.normalizeDocument();
-       }
-
-       @Override
-       public Content get() {
-               return new DomContent(this, document.getDocumentElement());
-       }
-
-       public static void main(String args[]) throws Exception {
-               HashMap<String, Object> map = new HashMap<>();
-               map.put(null, "test");
-               System.out.println(map.get(null));
-
-               Set<String> set = new HashSet<>();
-               set.add(null);
-
-               DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-               DocumentBuilder dBuilder = factory.newDocumentBuilder();
-               Path testFile;
-               testFile = Paths.get(System.getProperty("user.home") + "/dev/git/unstable/argeo-commons/pom.xml");
-               testFile = Paths.get(System.getProperty("user.home") + "/tmp/test.xml");
-               Document doc = dBuilder.parse(Files.newInputStream(testFile));
-
-               DomContentSession contentSession = new DomContentSession(doc);
-               ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
-
-       }
-}
index a5bde1c5df9f1551582930d90ca22e3374517031..e3efcb4b3f9d620a9b3553be29b045902a5ef900 100644 (file)
@@ -9,14 +9,14 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 public class ElementIterator implements Iterator<Content> {
-       private final DomContentSession contentSession;
+       private final DomContentProvider contentSession;
        private final NodeList nodeList;
 
        private int currentIndex;
        private final int length;
        private Element nextElement = null;
 
-       public ElementIterator(DomContentSession contentSession, NodeList nodeList) {
+       public ElementIterator(DomContentProvider contentSession, NodeList nodeList) {
                this.contentSession = contentSession;
                this.nodeList = nodeList;
 
diff --git a/org.argeo.util/src/org/argeo/util/BasicSyncFileVisitor.java b/org.argeo.util/src/org/argeo/util/BasicSyncFileVisitor.java
new file mode 100644 (file)
index 0000000..839d38a
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+
+/** Synchronises two directory structures. */
+public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
+       // TODO make it configurable
+       private boolean trace = false;
+
+       private final Path sourceBasePath;
+       private final Path targetBasePath;
+       private final boolean delete;
+       private final boolean recursive;
+
+       private SyncResult<Path> syncResult = new SyncResult<>();
+
+       public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+               this.sourceBasePath = sourceBasePath;
+               this.targetBasePath = targetBasePath;
+               this.delete = delete;
+               this.recursive = recursive;
+       }
+
+       @Override
+       public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
+               if (!recursive && !sourceDir.equals(sourceBasePath))
+                       return FileVisitResult.SKIP_SUBTREE;
+               Path targetDir = toTargetPath(sourceDir);
+               Files.createDirectories(targetDir);
+               return FileVisitResult.CONTINUE;
+       }
+
+       @Override
+       public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
+               if (delete) {
+                       Path targetDir = toTargetPath(sourceDir);
+                       for (Path targetPath : Files.newDirectoryStream(targetDir)) {
+                               Path sourcePath = sourceDir.resolve(targetPath.getFileName());
+                               if (!Files.exists(sourcePath)) {
+                                       try {
+                                               FsUtils.delete(targetPath);
+                                               deleted(targetPath);
+                                       } catch (Exception e) {
+                                               deleteFailed(targetPath, exc);
+                                       }
+                               }
+                       }
+               }
+               return FileVisitResult.CONTINUE;
+       }
+
+       @Override
+       public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
+               Path targetFile = toTargetPath(sourceFile);
+               try {
+                       if (!Files.exists(targetFile)) {
+                               Files.copy(sourceFile, targetFile);
+                               added(sourceFile, targetFile);
+                       } else {
+                               if (shouldOverwrite(sourceFile, targetFile)) {
+                                       Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+                               }
+                       }
+               } catch (Exception e) {
+                       copyFailed(sourceFile, targetFile, e);
+               }
+               return FileVisitResult.CONTINUE;
+       }
+
+       protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
+               long sourceSize = Files.size(sourceFile);
+               long targetSize = Files.size(targetFile);
+               if (sourceSize != targetSize) {
+                       return true;
+               }
+               FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
+               FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
+               if (sourceLastModif.compareTo(targetLastModif) > 0)
+                       return true;
+               return shouldOverwriteLaterSameSize(sourceFile, targetFile);
+       }
+
+       protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
+               return false;
+       }
+
+//     @Override
+//     public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
+//             error("Cannot sync " + sourceFile, exc);
+//             return FileVisitResult.CONTINUE;
+//     }
+
+       private Path toTargetPath(Path sourcePath) {
+               Path relativePath = sourceBasePath.relativize(sourcePath);
+               Path targetPath = targetBasePath.resolve(relativePath.toString());
+               return targetPath;
+       }
+
+       public Path getSourceBasePath() {
+               return sourceBasePath;
+       }
+
+       public Path getTargetBasePath() {
+               return targetBasePath;
+       }
+
+       protected void added(Path sourcePath, Path targetPath) {
+               syncResult.getAdded().add(targetPath);
+               if (isTraceEnabled())
+                       trace("Added " + sourcePath + " as " + targetPath);
+       }
+
+       protected void modified(Path sourcePath, Path targetPath) {
+               syncResult.getModified().add(targetPath);
+               if (isTraceEnabled())
+                       trace("Overwritten from " + sourcePath + " to " + targetPath);
+       }
+
+       protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
+               syncResult.addError(sourcePath, targetPath, e);
+               if (isTraceEnabled())
+                       error("Cannot copy " + sourcePath + " to " + targetPath, e);
+       }
+
+       protected void deleted(Path targetPath) {
+               syncResult.getDeleted().add(targetPath);
+               if (isTraceEnabled())
+                       trace("Deleted " + targetPath);
+       }
+
+       protected void deleteFailed(Path targetPath, Exception e) {
+               syncResult.addError(null, targetPath, e);
+               if (isTraceEnabled())
+                       error("Cannot delete " + targetPath, e);
+       }
+
+       /** Log error. */
+       protected void error(Object obj, Throwable e) {
+               System.err.println(obj);
+               e.printStackTrace();
+       }
+
+       protected boolean isTraceEnabled() {
+               return trace;
+       }
+
+       protected void trace(Object obj) {
+               System.out.println(obj);
+       }
+
+       public SyncResult<Path> getSyncResult() {
+               return syncResult;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/FsUtils.java b/org.argeo.util/src/org/argeo/util/FsUtils.java
new file mode 100644 (file)
index 0000000..2d1363b
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/** Utilities around the standard Java file abstractions. */
+public class FsUtils {
+       /** Sync a source path with a target path. */
+       public static void sync(Path sourceBasePath, Path targetBasePath) {
+               sync(sourceBasePath, targetBasePath, false);
+       }
+
+       /** Sync a source path with a target path. */
+       public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
+               sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
+       }
+
+       public static void sync(BasicSyncFileVisitor syncFileVisitor) {
+               try {
+                       Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with "
+                                       + syncFileVisitor.getTargetBasePath(), e);
+               }
+       }
+
+       /**
+        * Deletes this path, recursively if needed. Does nothing if the path does not
+        * exist.
+        */
+       public static void delete(Path path) {
+               try {
+                       if (!Files.exists(path))
+                               return;
+                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                               @Override
+                               public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+                                       if (e != null)
+                                               throw e;
+                                       Files.delete(directory);
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       Files.delete(file);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot delete " + path, e);
+               }
+       }
+
+       /** Singleton. */
+       private FsUtils() {
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/SyncResult.java b/org.argeo.util/src/org/argeo/util/SyncResult.java
new file mode 100644 (file)
index 0000000..0f2256b
--- /dev/null
@@ -0,0 +1,101 @@
+package org.argeo.util;
+
+import java.time.Instant;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Describes what happendend during a sync operation. */
+public class SyncResult<T> {
+       private final Set<T> added = new TreeSet<>();
+       private final Set<T> modified = new TreeSet<>();
+       private final Set<T> deleted = new TreeSet<>();
+       private final Set<Error> errors = new TreeSet<>();
+
+       public Set<T> getAdded() {
+               return added;
+       }
+
+       public Set<T> getModified() {
+               return modified;
+       }
+
+       public Set<T> getDeleted() {
+               return deleted;
+       }
+
+       public Set<Error> getErrors() {
+               return errors;
+       }
+
+       public void addError(T sourcePath, T targetPath, Exception e) {
+               Error error = new Error(sourcePath, targetPath, e);
+               errors.add(error);
+       }
+
+       public boolean noModification() {
+               return modified.isEmpty() && deleted.isEmpty() && added.isEmpty();
+       }
+
+       @Override
+       public String toString() {
+               if (noModification())
+                       return "No modification.";
+               StringBuffer sb = new StringBuffer();
+               for (T p : modified)
+                       sb.append("MOD ").append(p).append('\n');
+               for (T p : deleted)
+                       sb.append("DEL ").append(p).append('\n');
+               for (T p : added)
+                       sb.append("ADD ").append(p).append('\n');
+               for (Error error : errors)
+                       sb.append(error).append('\n');
+               return sb.toString();
+       }
+
+       public class Error implements Comparable<Error> {
+               private final T sourcePath;// if null this is a failed delete
+               private final T targetPath;
+               private final Exception exception;
+               private final Instant timestamp = Instant.now();
+
+               public Error(T sourcePath, T targetPath, Exception e) {
+                       super();
+                       this.sourcePath = sourcePath;
+                       this.targetPath = targetPath;
+                       this.exception = e;
+               }
+
+               public T getSourcePath() {
+                       return sourcePath;
+               }
+
+               public T getTargetPath() {
+                       return targetPath;
+               }
+
+               public Exception getException() {
+                       return exception;
+               }
+
+               public Instant getTimestamp() {
+                       return timestamp;
+               }
+
+               @Override
+               public int compareTo(Error o) {
+                       return timestamp.compareTo(o.timestamp);
+               }
+
+               @Override
+               public int hashCode() {
+                       return timestamp.hashCode();
+               }
+
+               @Override
+               public String toString() {
+                       return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " "
+                                       + targetPath + " " + exception.getMessage();
+               }
+
+       }
+}
index d72c31e9fb90d2a5ae2e88170848abd0c73aab8e..ea163d6a4d3b41f1b3f2b8407fa678ba7a34c3ff 100644 (file)
@@ -3,6 +3,12 @@ package org.argeo.util.naming;
 interface NodeOID {
        String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
 
+       // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
+       String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
+       
+       // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
+       String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
+       
        // ATTRIBUTE TYPES
        String ATTRIBUTE_TYPES = BASE + ".4";