From 51efb630db7314b67654a03d1bd983b45aa2f1ed Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 18 Jan 2022 10:21:19 +0100 Subject: [PATCH] Introduce CRUD to GCR --- .../src/org/argeo/api/gcr/Content.java | 15 +- .../src/org/argeo/api/gcr/ContentName.java | 19 ++ .../org/argeo/api/gcr/ContentNamespace.java | 106 ++++++++++++ .../src/org/argeo/api/gcr/ContentSession.java | 2 + .../src/org/argeo/api/gcr/ContentSystem.java | 5 - .../argeo/api/gcr/ContentSystemProvider.java | 7 - .../src/org/argeo/api/gcr/CrName.java | 42 +++++ .../argeo/api/gcr/spi/AbstractContent.java | 124 +++++++++++--- .../argeo/api/gcr/spi/ContentProvider.java | 11 ++ .../src/org/argeo/cms/jcr/gcr/JcrContent.java | 65 +++++-- .../argeo/cms/jcr/gcr/JcrContentProvider.java | 34 ++++ .../cms/jcr/gcr/JcrContentRepository.java | 35 ---- .../argeo/cms/jcr/gcr/JcrContentSession.java | 47 ----- .../argeo/cms/swt/gcr/GcrContentTreeView.java | 4 +- .../argeo/cms/gcr/CmsContentRepository.java | 63 +++++++ .../src/org/argeo/cms/gcr/fs/FsContent.java | 118 ++++++++++--- .../argeo/cms/gcr/fs/FsContentProvider.java | 36 ++++ .../argeo/cms/gcr/fs/FsContentSession.java | 32 ---- .../src/org/argeo/cms/gcr/xml/DomContent.java | 43 ++++- ...ntSession.java => DomContentProvider.java} | 19 +- .../argeo/cms/gcr/xml/ElementIterator.java | 4 +- .../org/argeo/util/BasicSyncFileVisitor.java | 162 ++++++++++++++++++ .../src/org/argeo/util/FsUtils.java | 63 +++++++ .../src/org/argeo/util/SyncResult.java | 101 +++++++++++ .../src/org/argeo/util/naming/NodeOID.java | 6 + 25 files changed, 967 insertions(+), 196 deletions(-) create mode 100644 org.argeo.api/src/org/argeo/api/gcr/ContentName.java create mode 100644 org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java delete mode 100644 org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java delete mode 100644 org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java create mode 100644 org.argeo.api/src/org/argeo/api/gcr/CrName.java create mode 100644 org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java create mode 100644 org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java delete mode 100644 org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java delete mode 100644 org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java create mode 100644 org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java create mode 100644 org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java delete mode 100644 org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java rename org.argeo.cms/src/org/argeo/cms/gcr/xml/{DomContentSession.java => DomContentProvider.java} (74%) create mode 100644 org.argeo.util/src/org/argeo/util/BasicSyncFileVisitor.java create mode 100644 org.argeo.util/src/org/argeo/util/FsUtils.java create mode 100644 org.argeo.util/src/org/argeo/util/SyncResult.java diff --git a/org.argeo.api/src/org/argeo/api/gcr/Content.java b/org.argeo.api/src/org/argeo/api/gcr/Content.java index b2fc3c906..9ce6ea4f6 100644 --- a/org.argeo.api/src/org/argeo/api/gcr/Content.java +++ b/org.argeo.api/src/org/argeo/api/gcr/Content.java @@ -9,11 +9,22 @@ public interface Content extends Iterable, Map { String getName(); -// Iterable keys(); + String getPath(); + + Content getParent(); + + /* + * ATTRIBUTES OPERATIONS + */ A get(String key, Class 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 index 000000000..ddc80c5b6 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentName.java @@ -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 index 000000000..a59557180 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java @@ -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); + } +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java index b7ffcd043..d779093be 100644 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java @@ -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 index 04ac11a5a..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java +++ /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 index 4f0d66dcc..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.api.gcr; - -import java.util.function.Supplier; - -public interface ContentSystemProvider extends Supplier { - -} 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 index 000000000..3fc148185 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/CrName.java @@ -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; + } + +} 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 index 2da186ef2..79c59f2f6 100644 --- a/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java +++ b/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java @@ -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 implem @Override public Set> entrySet() { - Set> result = new HashSet<>(); - for (String key : keys()) { - Entry entry = new Entry() { +// Set> result = new HashSet<>(); +// for (String key : keys()) { +// Entry entry = new Entry() { +// +// @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> result = new AttrSet(); + return result; + } + + protected abstract Iterable keys(); + + protected abstract void removeAttr(String key); + + @Override + public String getPath() { + List 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 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> { + + @Override + public Iterator> iterator() { + final Iterator keys = keys().iterator(); + Iterator> it = new Iterator>() { + + String key = null; @Override - public String getKey() { - return key; + public boolean hasNext() { + return keys.hasNext(); } @Override - public Object getValue() { + public Entry next() { + key = keys.next(); // TODO check type - return get(key, Object.class); + Object value = get(key, Object.class); + AbstractMap.SimpleEntry 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 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 index 000000000..973131948 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java @@ -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 get(String relativePath); + +} 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 index b136a0105..d319c5504 100644 --- 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 @@ -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 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 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 { - 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 { - 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 index 000000000..5e4a1b337 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java @@ -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 index 057ed3089..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java +++ /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 index 98ecbc887..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java +++ /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) () -> { - 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; - } - -} 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 index 354be5353..092a35739 100644 --- 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 @@ -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 index 000000000..0baae356c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java @@ -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 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 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; + } + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java index bb131f5aa..62ae613f0 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java @@ -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 BASIC_KEYS = new HashSet<>( Arrays.asList("basic:creationTime", "basic:lastModifiedTime", "basic:size", "basic:fileKey")); private static final Set 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 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 get(String key, Class 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 keys() { Set 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 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 index 000000000..d15f9c32b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java @@ -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 index 75ec19172..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java +++ /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)); - - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java index b42e0cf0d..30d590df4 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java @@ -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 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/DomContentSession.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java similarity index 74% rename from org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java rename to org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java index 1f8c0e90c..d1a625e32 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java @@ -11,14 +11,15 @@ 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.argeo.api.gcr.spi.ContentProvider; import org.w3c.dom.Document; +import org.w3c.dom.Element; -public class DomContentSession implements ContentSystemProvider { +public class DomContentProvider implements ContentProvider { private Document document; - public DomContentSession(Document document) { + public DomContentProvider(Document document) { this.document = document; this.document.normalizeDocument(); } @@ -28,6 +29,16 @@ public class DomContentSession implements ContentSystemProvider { 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 map = new HashMap<>(); map.put(null, "test"); @@ -43,7 +54,7 @@ public class DomContentSession implements ContentSystemProvider { testFile = Paths.get(System.getProperty("user.home") + "/tmp/test.xml"); Document doc = dBuilder.parse(Files.newInputStream(testFile)); - DomContentSession contentSession = new DomContentSession(doc); + 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/ElementIterator.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java index a5bde1c5d..e3efcb4b3 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java @@ -9,14 +9,14 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class ElementIterator implements Iterator { - 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 index 000000000..839d38af7 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/BasicSyncFileVisitor.java @@ -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 { + // 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 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 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 index 000000000..2d1363b61 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/FsUtils.java @@ -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() { + @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 index 000000000..0f2256b6c --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/SyncResult.java @@ -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 { + private final Set added = new TreeSet<>(); + private final Set modified = new TreeSet<>(); + private final Set deleted = new TreeSet<>(); + private final Set errors = new TreeSet<>(); + + public Set getAdded() { + return added; + } + + public Set getModified() { + return modified; + } + + public Set getDeleted() { + return deleted; + } + + public Set 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 { + 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(); + } + + } +} diff --git a/org.argeo.util/src/org/argeo/util/naming/NodeOID.java b/org.argeo.util/src/org/argeo/util/naming/NodeOID.java index d72c31e9f..ea163d6a4 100644 --- a/org.argeo.util/src/org/argeo/util/naming/NodeOID.java +++ b/org.argeo.util/src/org/argeo/util/naming/NodeOID.java @@ -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"; -- 2.30.2