Introduce namespace support
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 20 Jan 2022 11:58:20 +0000 (12:58 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 20 Jan 2022 11:58:20 +0000 (12:58 +0100)
26 files changed:
org.argeo.api/src/org/argeo/api/gcr/CompositeString.java
org.argeo.api/src/org/argeo/api/gcr/Content.java
org.argeo.api/src/org/argeo/api/gcr/ContentMetadata.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentName.java
org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentNotFoundException.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/ContentSession.java
org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java
org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/CrName.java
org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java
org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java
org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java [new file with mode: 0644]
org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.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
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java
org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java
org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java

index 1533c10ce22d9c83efe354d1edcc0e1efa70c42c..f979357b1bc6d685950a0ff4d5bcfbfdcb152f64 100644 (file)
@@ -97,7 +97,13 @@ public class CompositeString {
                        while (st.hasMoreTokens()) {
                                res.add(st.nextToken().toLowerCase());
                        }
-               } else {// CAML
+               } else {
+                       // single
+                       String strLowerCase = str.toLowerCase();
+                       if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
+                               return new String[] { strLowerCase };
+
+                       // CAML
                        StringBuilder current = null;
                        for (char c : str.toCharArray()) {
                                if (Character.isUpperCase(c)) {
@@ -129,6 +135,13 @@ public class CompositeString {
        }
 
        static boolean smokeTests() {
+               CompositeString plainName = new CompositeString("NAME");
+               assert "name".equals(plainName.toString());
+               assert "NAME".equals(plainName.toString(UNDERSCORE, true));
+               assert "name".equals(plainName.toString(UNDERSCORE, false));
+               assert "name".equals(plainName.toStringCaml(false));
+               assert "Name".equals(plainName.toStringCaml(true));
+
                CompositeString camlName = new CompositeString("myComplexName");
 
                assert new CompositeString("my-complex-name").equals(camlName);
@@ -138,16 +151,14 @@ public class CompositeString {
 
                assert "my-complex-name".equals(camlName.toString());
                assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
+               assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
                assert "myComplexName".equals(camlName.toStringCaml(false));
                assert "MyComplexName".equals(camlName.toStringCaml(true));
 
-               CompositeString plainName = new CompositeString("name");
-               assert "name".equals(plainName.toString());
-
                return CompositeString.class.desiredAssertionStatus();
        }
 
-//     public static void main(String[] args) {
-//             System.out.println(smokeTests());
-//     }
+       public static void main(String[] args) {
+               System.out.println(smokeTests());
+       }
 }
index 9ce6ea4f6b3f70f4add3ac5895c5e603523622de..e35f42f27ab7519d1122790b009ac3b65121d6ff 100644 (file)
@@ -2,12 +2,15 @@ package org.argeo.api.gcr;
 
 import java.util.Map;
 
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+
 /**
  * A semi-structured content, with attributes, within a hierarchical structure.
  */
-public interface Content extends Iterable<Content>, Map<String, Object> {
+public interface Content extends Iterable<Content>, Map<QName, Object> {
 
-       String getName();
+       QName getName();
 
        String getPath();
 
@@ -17,12 +20,36 @@ public interface Content extends Iterable<Content>, Map<String, Object> {
         * ATTRIBUTES OPERATIONS
         */
 
-       <A> A get(String key, Class<A> clss) throws IllegalArgumentException;
+       <A> A get(QName key, Class<A> clss) throws IllegalArgumentException;
+
+       default Object get(String key) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       }
+
+       default Object put(String key, Object value) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
+       }
+
+       default Object remove(String key) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       }
 
        /*
         * CONTENT OPERATIONS
         */
-       Content add(String name, ContentName... classes);
+       Content add(QName name, QName... classes);
+
+       default Content add(String name, QName... classes) {
+               if (name.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + name + " has a prefix");
+               return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
+       }
 
        void remove();
 
@@ -40,17 +67,17 @@ public interface Content extends Iterable<Content>, Map<String, Object> {
        /*
         * CONVENIENCE METHODS
         */
-       default String attr(String key) {
-               return get(key, String.class);
-       }
-
-       default String attr(Object key) {
-               return key != null ? attr(key.toString()) : attr(null);
-       }
-
-       default <A> A get(Object key, Class<A> clss) {
-               return key != null ? get(key.toString(), clss) : get(null, clss);
-       }
+//     default String attr(String key) {
+//             return get(key, String.class);
+//     }
+//
+//     default String attr(Object key) {
+//             return key != null ? attr(key.toString()) : attr(null);
+//     }
+//
+//     default <A> A get(Object key, Class<A> clss) {
+//             return key != null ? get(key.toString(), clss) : get(null, clss);
+//     }
 
        /*
         * EXPERIMENTAL UNSUPPORTED
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentMetadata.java b/org.argeo.api/src/org/argeo/api/gcr/ContentMetadata.java
deleted file mode 100644 (file)
index 841887f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.api.gcr;
-
-import java.io.Serializable;
-import java.util.Set;
-
-public interface ContentMetadata extends Serializable {
-       Set<String> getKnownKeys();
-
-       Set<String> getTypes();
-
-       /** Whether that content can have unknown keys. */
-       default boolean isStructured() {
-               return false;
-       }
-}
index ddc80c5b621aae1f7766b3d63d2062a1490a671f..5acd53a00a8b506cb4c5e5b9f0a0867500784775 100644 (file)
 package org.argeo.api.gcr;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
 import java.util.UUID;
 
-public interface ContentName {
-       UUID getUuid();
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+/**
+ * A {@link QName} which MUST have prefix and whose {@link #toString()} method
+ * returns the prefixed form (prefix:localPart).
+ */
+public class ContentName extends QName {
+       private static final long serialVersionUID = 5722920985400306100L;
+       public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+       /**
+        * 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");
+       // uuidgen --md5 --namespace 4b352aad-ba1c-3139-b9d3-41e5816f6088 --name ""
+       public final static UUID NULL_NS_UUID = UUID.fromString("f07726e3-99c8-3178-b758-a86ed41f300d");
+
+       private final static Map<String, UUID> namespaceUuids = Collections.synchronizedMap(new TreeMap<>());
+       private final static Map<String, UUID> nameUuids = Collections.synchronizedMap(new TreeMap<>());
+
+       static {
+               assert NULL_NS_UUID.equals(nameUUIDv3(XMLNS_UUID, XMLConstants.NULL_NS_URI.getBytes(UTF_8)));
+       }
+
+//     private final UUID uuid;
+
+       public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
+               this(namespaceURI, localPart, nsContext.getPrefix(namespaceURI));
+       }
+
+       protected ContentName(String namespaceURI, String localPart, String prefix) {
+               super(namespaceURI, localPart, prefix);
+               if (prefix == null)
+                       throw new IllegalArgumentException("Prefix annot be null");
+       }
+
+       public ContentName(String localPart) {
+               this(XMLConstants.NULL_NS_URI, localPart, XMLConstants.DEFAULT_NS_PREFIX);
+       }
+
+       public ContentName(QName qName, NamespaceContext nsContext) {
+               this(qName.getNamespaceURI(), qName.getLocalPart(), nsContext);
+       }
+
+       public String toQNameString() {
+               return super.toString();
+       }
+
+       public String toPrefixedString() {
+               return toPrefixedString(this);
+       }
+
+       /*
+        * OBJECT METHOS
+        */
+
+       @Override
+       public String toString() {
+               return toPrefixedString();
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new ContentName(getNamespaceURI(), getLocalPart(), getPrefix());
+       }
+
+       public static String toPrefixedString(QName name) {
+               String prefix = name.getPrefix();
+               assert prefix != null;
+               return "".equals(prefix) ? name.getLocalPart() : prefix + ":" + name.getLocalPart();
+       }
+//     ContentNamespace getNamespace();
+//
+//     String getName();
+
+       public static UUID namespaceUuid(String namespaceURI) {
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return NULL_NS_UUID;
+               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+               synchronized (namespaceUuids) {
+                       UUID namespaceUuid = namespaceUuids.get(namespaceURI);
+                       if (namespaceUuid == null) {
+                               namespaceUuid = nameUUIDv3(ContentName.XMLNS_UUID,
+                                               namespaceURI.toString().getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(namespaceURI, namespaceUuid);
+                       }
+                       return namespaceUuid;
+               }
+       }
+
+       public static UUID nameUuid(String namespaceURI, QName name) {
+               return nameUuid(name.getNamespaceURI(), name.getLocalPart());
+       }
 
-       ContentNamespace getNamespace();
+       public static UUID nameUuid(String namespaceURI, String name) {
+               Objects.requireNonNull(namespaceURI, "Namespace cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
+               synchronized (nameUuids) {
+                       String key = XMLConstants.NULL_NS_URI.equals(namespaceURI) ? name : "{" + namespaceURI + "}" + name;
+                       UUID nameUuid = nameUuids.get(key);
+                       if (nameUuid == null) {
+                               UUID namespaceUuid = namespaceUuid(namespaceURI);
+                               nameUuid = nameUUIDv3(namespaceUuid, name.getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(key, nameUuid);
+                       }
+                       return nameUuid;
+               }
+       }
+
+       /*
+        * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+        */
+       static UUID nameUUIDv3(UUID namespace, byte[] name) {
+               byte[] arr = new byte[name.length + 16];
+               ContentName.copyUuidBytes(namespace, arr, 0);
+               System.arraycopy(name, 0, arr, 16, name.length);
+               return UUID.nameUUIDFromBytes(arr);
+       }
+
+       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);
+       }
 
-       String getName();
+       /*
+        * UTILITIES
+        */
 
-       static boolean contains(ContentName[] classes, ContentName name) {
-               for (ContentName clss : classes) {
-                       if (clss.getUuid().equals(name.getUuid()))
+       public static boolean contains(QName[] classes, QName name) {
+               for (QName clss : classes) {
+                       if (clss.equals(name))
                                return true;
                }
                return false;
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java b/org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java
new file mode 100644 (file)
index 0000000..b6d8020
--- /dev/null
@@ -0,0 +1,102 @@
+package org.argeo.api.gcr;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.function.Supplier;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+public interface ContentNameSupplier extends Supplier<ContentName>, NamespaceContext {
+       String name();
+
+       @Override
+       default ContentName get() {
+               return toContentName();
+       }
+
+       default ContentName toContentName() {
+               CompositeString cs = new CompositeString(name());
+               String camlName = cs.toStringCaml(false);
+               return new ContentName(getNamespaceURI(), camlName, this);
+       }
+
+       default String getNamespaceURI() {
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       default String getDefaultPrefix() {
+               return XMLConstants.DEFAULT_NS_PREFIX;
+       }
+
+//     static ContentName toContentName(String namespaceURI, String localName, String prefix) {
+//             CompositeString cs = new CompositeString(localName);
+//             String camlName = cs.toStringCaml(false);
+//             return new ContentName(namespaceURI, camlName, this);
+//     }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       @Override
+       default String getNamespaceURI(String prefix) {
+               String namespaceURI = getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (prefix.equals(getDefaultPrefix()))
+                       return getNamespaceURI();
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       @Override
+       default String getPrefix(String namespaceURI) {
+               String prefix = getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (namespaceURI.equals(getNamespaceURI()))
+                       return getDefaultPrefix();
+               return null;
+       }
+
+       @Override
+       default Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> it = getStandardPrefixes(namespaceURI);
+               if (it != null)
+                       return it;
+               if (namespaceURI.equals(getNamespaceURI()))
+                       return Collections.singleton(getDefaultPrefix()).iterator();
+               return Collections.emptyIterator();
+       }
+
+       /*
+        * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
+        */
+       static String getStandardPrefix(String namespaceURI) {
+               if (namespaceURI == null)
+                       throw new IllegalArgumentException("Namespace URI cannot be null");
+               if (XMLConstants.XML_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XML_NS_PREFIX;
+               else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XMLNS_ATTRIBUTE;
+               return null;
+       }
+
+       static Iterator<String> getStandardPrefixes(String namespaceURI) {
+               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
+               if (prefix == null)
+                       return null;
+               return Collections.singleton(prefix).iterator();
+       }
+
+       static String getStandardNamespaceURI(String prefix) {
+               if (prefix == null)
+                       throw new IllegalArgumentException("Prefix cannot be null");
+               if (XMLConstants.XML_NS_PREFIX.equals(prefix))
+                       return XMLConstants.XML_NS_URI;
+               else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
+                       return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+               return null;
+       }
+
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java b/org.argeo.api/src/org/argeo/api/gcr/ContentNamespace.java
deleted file mode 100644 (file)
index a595571..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-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/ContentNotFoundException.java b/org.argeo.api/src/org/argeo/api/gcr/ContentNotFoundException.java
new file mode 100644 (file)
index 0000000..8a09093
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.api.gcr;
+
+/** When a countent was requested which does not exists, equivalent to HTTP code 404.*/
+public class ContentNotFoundException extends RuntimeException {
+       private static final long serialVersionUID = -8629074900713760886L;
+
+       public ContentNotFoundException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+       public ContentNotFoundException(String message) {
+               super(message);
+       }
+
+       
+}
index d779093be863c160914e32a22d5fcca415875431..6c80189b24aebecafb4211d6f2a30a9bad05e8ba 100644 (file)
@@ -1,13 +1,49 @@
 package org.argeo.api.gcr;
 
 import java.util.Locale;
+import java.util.Objects;
 
 import javax.security.auth.Subject;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
 
-public interface ContentSession {
+public interface ContentSession extends NamespaceContext {
        Subject getSubject();
 
        Locale getLocale();
 
        Content get(String path);
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       default ContentName parsePrefixedName(String nameWithPrefix) {
+               Objects.requireNonNull(nameWithPrefix, "Name cannot be null");
+               if (nameWithPrefix.charAt(0) == '{') {
+                       return new ContentName(QName.valueOf(nameWithPrefix), this);
+               }
+               int index = nameWithPrefix.indexOf(':');
+               if (index < 0) {
+                       return new ContentName(nameWithPrefix);
+               }
+               String prefix = nameWithPrefix.substring(0, index);
+               // TODO deal with empty name?
+               String localName = nameWithPrefix.substring(index + 1);
+               String namespaceURI = getNamespaceURI(prefix);
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       throw new IllegalStateException("Prefix " + prefix + " is unbound.");
+               return new ContentName(namespaceURI, localName, prefix);
+       }
+
+       default String toPrefixedName(QName name) {
+               if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()))
+                       return name.getLocalPart();
+               String prefix = getPrefix(name.getNamespaceURI());
+               if (prefix == null)
+                       throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound.");
+               return prefix + ":" + name.getLocalPart();
+       }
+
 }
index 01949347fb958946acda415aef5843061ad7bee2..cf5115590844de1a5f62f4119200f4804e29d484 100644 (file)
@@ -8,6 +8,8 @@ import java.util.Base64;
 import java.util.List;
 import java.util.function.BiConsumer;
 
+import javax.xml.namespace.QName;
+
 public class ContentUtils {
        public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
                traverse(content, doIt, 0);
@@ -28,7 +30,7 @@ public class ContentUtils {
                }
                String prefix = sb.toString();
                out.println(prefix + content.getName());
-               for (String key : content.keySet()) {
+               for (QName key : content.keySet()) {
                        out.println(prefix + " " + key + "=" + content.get(key));
                }
                if (printText) {
diff --git a/org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java b/org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java
new file mode 100644 (file)
index 0000000..28f1bc0
--- /dev/null
@@ -0,0 +1,183 @@
+package org.argeo.api.gcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.UUID;
+
+/**
+ * Minimal standard attribute types that MUST be supported. All related classes
+ * belong to java.base and can be implicitly derived form a given
+ * <code>String<code>.
+ */
+public enum CrAttributeType {
+       BOOLEAN(Boolean.class, new BooleanFormatter()), //
+       INTEGER(Integer.class, new IntegerFormatter()), //
+       LONG(Long.class, new LongFormatter()), //
+       DOUBLE(Double.class, new DoubleFormatter()), //
+       // we do not support short and float, like recent additions to Java
+       // (e.g. optional primitives)
+       INSTANT(Instant.class, new InstantFormatter()), //
+       UUID(UUID.class, new UuidFormatter()), //
+       URI(URI.class, new UriFormatter()), //
+       STRING(String.class, new StringFormatter()), //
+       ;
+
+       private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+               this.clss = clss;
+               this.formatter = formatter;
+       }
+
+       private final Class<?> clss;
+       private final AttributeFormatter<?> formatter;
+
+       public Class<?> getClss() {
+               return clss;
+       }
+
+       public AttributeFormatter<?> getFormatter() {
+               return formatter;
+       }
+
+       public static Object parse(String str) {
+               if (str == null)
+                       throw new IllegalArgumentException("String cannot be null");
+               // order IS important
+               try {
+                       if (str.length() == 4 || str.length() == 5)
+                               return BOOLEAN.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return INTEGER.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return LONG.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return DOUBLE.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return INSTANT.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       if (str.length() == 36)
+                               return UUID.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       java.net.URI uri = (java.net.URI) URI.getFormatter().parse(str);
+                       if (uri.getScheme() != null)
+                               return uri;
+                       String path = uri.getPath();
+                       if (path.indexOf('/') >= 0)
+                               return uri;
+                       // if it is not clearly a path, we will consider it as a string
+                       // because their is no way to distinguish between 'any_string'
+                       // and 'any_file_name'.
+                       // Note that providing ./any_file_name would result in an equivalent URI
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+
+               // default
+               return STRING.getFormatter().parse(str);
+       }
+
+       static class BooleanFormatter implements AttributeFormatter<Boolean> {
+
+               /**
+                * @param str must be exactly equals to either 'true' or 'false' (different
+                *            contract than {@link Boolean#parseBoolean(String)}.
+                */
+               @Override
+               public Boolean parse(String str) throws IllegalArgumentException {
+                       if ("true".equals(str))
+                               return Boolean.TRUE;
+                       if ("false".equals(str))
+                               return Boolean.FALSE;
+                       throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
+               }
+       }
+
+       static class IntegerFormatter implements AttributeFormatter<Integer> {
+               @Override
+               public Integer parse(String str) throws NumberFormatException {
+                       return Integer.parseInt(str);
+               }
+       }
+
+       static class LongFormatter implements AttributeFormatter<Long> {
+               @Override
+               public Long parse(String str) throws NumberFormatException {
+                       return Long.parseLong(str);
+               }
+       }
+
+       static class DoubleFormatter implements AttributeFormatter<Double> {
+
+               @Override
+               public Double parse(String str) throws NumberFormatException {
+                       return Double.parseDouble(str);
+               }
+       }
+
+       static class InstantFormatter implements AttributeFormatter<Instant> {
+
+               @Override
+               public Instant parse(String str) throws IllegalArgumentException {
+                       try {
+                               return Instant.parse(str);
+                       } catch (DateTimeParseException e) {
+                               throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
+                       }
+               }
+       }
+
+       static class UuidFormatter implements AttributeFormatter<UUID> {
+
+               @Override
+               public UUID parse(String str) throws IllegalArgumentException {
+                       return java.util.UUID.fromString(str);
+               }
+       }
+
+       static class UriFormatter implements AttributeFormatter<URI> {
+
+               @Override
+               public URI parse(String str) throws IllegalArgumentException {
+                       try {
+                               return new URI(str);
+                       } catch (URISyntaxException e) {
+                               throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
+                       }
+               }
+
+       }
+
+       static class StringFormatter implements AttributeFormatter<String> {
+
+               @Override
+               public String parse(String str) {
+                       return str;
+               }
+
+               @Override
+               public String format(String obj) {
+                       return obj;
+               }
+
+       }
+
+}
index 3fc1481856643e6a57cdd1999ef3e4e18f1d5740..283e5cc716f09c6c8a7af6c8dc4bc1f31c969e85 100644 (file)
@@ -1,42 +1,58 @@
 package org.argeo.api.gcr;
 
-import java.util.UUID;
-
 /** Standard names. */
-public enum CrName implements ContentName {
+public enum CrName implements ContentNameSupplier {
+
        /*
         * TYPES
         */
-       COLLECTION("collection"), // a collection type
+       COLLECTION, // a collection type
 
        /*
         * ATTRIBUTES
         */
-       UUID("uuid"), // the UUID of a content
+       UUID, // the UUID of a content
+
+       /*
+        * ATTRIBUTES FROM FILE SEMANTICS
+        */
+       CREATION_TIME, //
+       LAST_MODIFIED_TIME, //
+       SIZE, //
+       FILE_KEY, //
+       OWNER, //
+       GROUP, //
+       PERMISSIONS, //
+
+       /*
+        * CONTENT NAMES
+        */
+       ROOT,
+
        //
        ;
 
-       private String name;
-       private UUID uuid;
+       public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr";
+       public final static String CR_DEFAULT_PREFIX = "cr";
+       private final ContentName value;
 
-       CrName(String name) {
-               this.name = name;
-               this.uuid = ContentNamespace.CR_NS.nameUuid(name);
+       CrName() {
+               value = toContentName();
        }
 
        @Override
-       public UUID getUuid() {
-               return uuid;
+       public ContentName get() {
+               return value;
        }
 
        @Override
-       public ContentNamespace getNamespace() {
-               return ContentNamespace.CR_NS;
+       public String getNamespaceURI() {
+               return CR_NAMESPACE_URI;
        }
 
        @Override
-       public String getName() {
-               return name;
+       public String getDefaultPrefix() {
+               return CR_DEFAULT_PREFIX;
        }
 
 }
diff --git a/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java b/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java
deleted file mode 100644 (file)
index ea70880..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.argeo.api.gcr;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.time.Instant;
-import java.time.format.DateTimeParseException;
-import java.util.UUID;
-
-/**
- * Minimal standard attribute types that MUST be supported. All related classes
- * belong to java.base and can be implicitly derived form a given
- * <code>String<code>.
- */
-public enum StandardAttributeType {
-       BOOLEAN(Boolean.class, new BooleanFormatter()), //
-       INTEGER(Integer.class, new IntegerFormatter()), //
-       LONG(Long.class, new LongFormatter()), //
-       DOUBLE(Double.class, new DoubleFormatter()), //
-       // we do not support short and float, like recent additions to Java
-       // (e.g. optional primitives)
-       INSTANT(Instant.class, new InstantFormatter()), //
-       UUID(UUID.class, new UuidFormatter()), //
-       URI(URI.class, new UriFormatter()), //
-       STRING(String.class, new StringFormatter()), //
-       ;
-
-       private <T> StandardAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
-               this.clss = clss;
-               this.formatter = formatter;
-       }
-
-       private final Class<?> clss;
-       private final AttributeFormatter<?> formatter;
-
-       public Class<?> getClss() {
-               return clss;
-       }
-
-       public AttributeFormatter<?> getFormatter() {
-               return formatter;
-       }
-
-       public static Object parse(String str) {
-               if (str == null)
-                       throw new IllegalArgumentException("String cannot be null");
-               // order IS important
-               try {
-                       if (str.length() == 4 || str.length() == 5)
-                               return BOOLEAN.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return INTEGER.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return LONG.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return DOUBLE.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return INSTANT.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       if (str.length() == 36)
-                               return UUID.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       java.net.URI uri = (java.net.URI) URI.getFormatter().parse(str);
-                       if (uri.getScheme() != null)
-                               return uri;
-                       String path = uri.getPath();
-                       if (path.indexOf('/') >= 0)
-                               return uri;
-                       // if it is not clearly a path, we will consider it as a string
-                       // because their is no way to distinguish between 'any_string'
-                       // and 'any_file_name'.
-                       // Note that providing ./any_file_name would result in an equivalent URI
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-
-               // default
-               return STRING.getFormatter().parse(str);
-       }
-
-       static class BooleanFormatter implements AttributeFormatter<Boolean> {
-
-               /**
-                * @param str must be exactly equals to either 'true' or 'false' (different
-                *            contract than {@link Boolean#parseBoolean(String)}.
-                */
-               @Override
-               public Boolean parse(String str) throws IllegalArgumentException {
-                       if ("true".equals(str))
-                               return Boolean.TRUE;
-                       if ("false".equals(str))
-                               return Boolean.FALSE;
-                       throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
-               }
-       }
-
-       static class IntegerFormatter implements AttributeFormatter<Integer> {
-               @Override
-               public Integer parse(String str) throws NumberFormatException {
-                       return Integer.parseInt(str);
-               }
-       }
-
-       static class LongFormatter implements AttributeFormatter<Long> {
-               @Override
-               public Long parse(String str) throws NumberFormatException {
-                       return Long.parseLong(str);
-               }
-       }
-
-       static class DoubleFormatter implements AttributeFormatter<Double> {
-
-               @Override
-               public Double parse(String str) throws NumberFormatException {
-                       return Double.parseDouble(str);
-               }
-       }
-
-       static class InstantFormatter implements AttributeFormatter<Instant> {
-
-               @Override
-               public Instant parse(String str) throws IllegalArgumentException {
-                       try {
-                               return Instant.parse(str);
-                       } catch (DateTimeParseException e) {
-                               throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
-                       }
-               }
-       }
-
-       static class UuidFormatter implements AttributeFormatter<UUID> {
-
-               @Override
-               public UUID parse(String str) throws IllegalArgumentException {
-                       return java.util.UUID.fromString(str);
-               }
-       }
-
-       static class UriFormatter implements AttributeFormatter<URI> {
-
-               @Override
-               public URI parse(String str) throws IllegalArgumentException {
-                       try {
-                               return new URI(str);
-                       } catch (URISyntaxException e) {
-                               throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
-                       }
-               }
-
-       }
-
-       static class StringFormatter implements AttributeFormatter<String> {
-
-               @Override
-               public String parse(String str) {
-                       return str;
-               }
-
-               @Override
-               public String format(String obj) {
-                       return obj;
-               }
-
-       }
-
-}
index 79c59f2f66ad06c0b1cf2ca74b0b6917f1610b9a..10f36acdf0509ac117871235faa92aea3bba8b9f 100644 (file)
@@ -8,12 +8,15 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import javax.xml.namespace.QName;
+
 import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.CrName;
 
-public abstract class AbstractContent extends AbstractMap<String, Object> implements Content {
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements Content {
 
        @Override
-       public Set<Entry<String, Object>> entrySet() {
+       public Set<Entry<QName, Object>> entrySet() {
 //             Set<Entry<String, Object>> result = new HashSet<>();
 //             for (String key : keys()) {
 //                     Entry<String, Object> entry = new Entry<String, Object>() {
@@ -36,13 +39,13 @@ public abstract class AbstractContent extends AbstractMap<String, Object> implem
 //                     };
 //                     result.add(entry);
 //             }
-               Set<Entry<String, Object>> result = new AttrSet();
+               Set<Entry<QName, Object>> result = new AttrSet();
                return result;
        }
 
-       protected abstract Iterable<String> keys();
+       protected abstract Iterable<QName> keys();
 
-       protected abstract void removeAttr(String key);
+       protected abstract void removeAttr(QName key);
 
        @Override
        public String getPath() {
@@ -50,8 +53,9 @@ public abstract class AbstractContent extends AbstractMap<String, Object> implem
                collectAncestors(ancestors, this);
                StringBuilder path = new StringBuilder();
                for (Content c : ancestors) {
-                       String name = c.getName();
-                       if (!"".equals(name))
+                       QName name = c.getName();
+                       // FIXME
+                       if (!CrName.ROOT.get().equals(name))
                                path.append('/').append(name);
                }
                return path.toString();
@@ -74,21 +78,21 @@ public abstract class AbstractContent extends AbstractMap<String, Object> implem
 
        @Override
        public String toString() {
-               return "content "+getPath();
+               return "content " + getPath();
        }
 
        /*
         * SUB CLASSES
         */
 
-       class AttrSet extends AbstractSet<Entry<String, Object>> {
+       class AttrSet extends AbstractSet<Entry<QName, 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>>() {
+               public Iterator<Entry<QName, Object>> iterator() {
+                       final Iterator<QName> keys = keys().iterator();
+                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
 
-                               String key = null;
+                               QName key = null;
 
                                @Override
                                public boolean hasNext() {
@@ -96,11 +100,11 @@ public abstract class AbstractContent extends AbstractMap<String, Object> implem
                                }
 
                                @Override
-                               public Entry<String, Object> next() {
+                               public Entry<QName, Object> next() {
                                        key = keys.next();
                                        // TODO check type
                                        Object value = get(key, Object.class);
-                                       AbstractMap.SimpleEntry<String, Object> entry = new SimpleEntry<>(key, value);
+                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value);
                                        return entry;
                                }
 
@@ -120,7 +124,7 @@ public abstract class AbstractContent extends AbstractMap<String, Object> implem
                @Override
                public int size() {
                        int count = 0;
-                       for (String key : keys()) {
+                       for (QName key : keys()) {
                                count++;
                        }
                        return count;
index 9731319487e67ccedee0e90ade7725c2fbb39f65..3a24ca24b0ba0e1e83686def2939776ca5f78b29 100644 (file)
@@ -1,11 +1,9 @@
 package org.argeo.api.gcr.spi;
 
-import java.util.function.Supplier;
-
 import org.argeo.api.gcr.Content;
 
-public interface ContentProvider extends Supplier<Content> {
+public interface ContentProvider {
 
-       Content get(String relativePath);
+       Content get(ProvidedSession session, String mountPath, String relativePath);
 
 }
diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java
new file mode 100644 (file)
index 0000000..e75f62d
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.gcr.spi;
+
+import org.argeo.api.gcr.Content;
+
+public interface ProvidedContent extends Content {
+       ProvidedSession getSession();
+
+       ContentProvider getProvider();
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java
new file mode 100644 (file)
index 0000000..2db13f9
--- /dev/null
@@ -0,0 +1,6 @@
+package org.argeo.api.gcr.spi;
+
+import org.argeo.api.gcr.ContentRepository;
+
+public interface ProvidedRepository extends ContentRepository {
+}
diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.java
new file mode 100644 (file)
index 0000000..4e93684
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.api.gcr.spi;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+import org.argeo.api.gcr.ContentNameSupplier;
+import org.argeo.api.gcr.ContentSession;
+
+public interface ProvidedSession extends ContentSession, NamespaceContext {
+       ProvidedRepository getRepository();
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+       /** @return the bound namespace or null if not found */
+       String findNamespace(String prefix);
+
+       // TODO find the default prefix?
+       Set<String> findPrefixes(String namespaceURI);
+
+       /** To be overridden for optimisation, as it will be called a lot */
+       default String findPrefix(String namespaceURI) {
+               Set<String> prefixes = findPrefixes(namespaceURI);
+               if (prefixes.isEmpty())
+                       return null;
+               return prefixes.iterator().next();
+       }
+
+       @Override
+       default String getNamespaceURI(String prefix) {
+               String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
+                       return XMLConstants.NULL_NS_URI;
+               namespaceURI = findNamespace(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       @Override
+       default String getPrefix(String namespaceURI) {
+               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return XMLConstants.DEFAULT_NS_PREFIX;
+               return findPrefix(namespaceURI);
+       }
+
+       @Override
+       default Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> standard = ContentNameSupplier.getStandardPrefixes(namespaceURI);
+               if (standard != null)
+                       return standard;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
+               Set<String> prefixes = findPrefixes(namespaceURI);
+               assert prefixes != null;
+               return prefixes.iterator();
+       }
+
+}
index d319c5504e4f32f4ecbd7d1b2313a5f94f118d73..ddef0078ef7264ca683fb45e333b602e2c6ea56d 100644 (file)
@@ -11,53 +11,57 @@ import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.nodetype.NodeType;
+import javax.xml.namespace.QName;
 
 import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentName;
 import org.argeo.api.gcr.spi.AbstractContent;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrException;
 
 public class JcrContent extends AbstractContent {
-       private JcrContentProvider contentProvider;
        private Node jcrNode;
 
-       protected JcrContent(JcrContentProvider contentSession, Node node) {
-               this.contentProvider = contentSession;
+       private JcrContentProvider provider;
+       private ProvidedSession session;
+
+       protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) {
+               this.session = session;
+               this.provider = provider;
                this.jcrNode = node;
        }
 
        @Override
-       public String getName() {
-               return Jcr.getName(jcrNode);
+       public QName getName() {
+               return session.parsePrefixedName(Jcr.getName(jcrNode));
        }
 
        @Override
-       public <A> A get(String key, Class<A> clss) {
+       public <A> A get(QName key, Class<A> clss) {
                if (isDefaultAttrTypeRequested(clss)) {
-                       return (A) get(jcrNode, key);
+                       return (A) get(jcrNode, key.toString());
                }
-               return (A) Jcr.get(jcrNode, key);
+               return (A) Jcr.get(jcrNode, key.toString());
        }
 
        @Override
        public Iterator<Content> iterator() {
                try {
-                       return new JcrContentIterator(contentProvider, jcrNode.getNodes());
+                       return new JcrContentIterator(jcrNode.getNodes());
                } catch (RepositoryException e) {
                        throw new JcrException("Cannot list children of " + jcrNode, e);
                }
        }
 
        @Override
-       protected Iterable<String> keys() {
-               return new Iterable<String>() {
+       protected Iterable<QName> keys() {
+               return new Iterable<QName>() {
 
                        @Override
-                       public Iterator<String> iterator() {
+                       public Iterator<QName> iterator() {
                                try {
                                        PropertyIterator propertyIterator = jcrNode.getProperties();
-                                       return new JcrKeyIterator(contentProvider, propertyIterator);
+                                       return new JcrKeyIterator(provider, propertyIterator);
                                } catch (RepositoryException e) {
                                        throw new JcrException("Cannot retrive properties from " + jcrNode, e);
                                }
@@ -95,14 +99,12 @@ public class JcrContent extends AbstractContent {
                }
        }
 
-       static class JcrContentIterator implements Iterator<Content> {
-               private final JcrContentProvider contentSession;
+       class JcrContentIterator implements Iterator<Content> {
                private final NodeIterator nodeIterator;
                // we keep track in order to be able to delete it
                private JcrContent current = null;
 
-               protected JcrContentIterator(JcrContentProvider contentSession, NodeIterator nodeIterator) {
-                       this.contentSession = contentSession;
+               protected JcrContentIterator(NodeIterator nodeIterator) {
                        this.nodeIterator = nodeIterator;
                }
 
@@ -113,7 +115,7 @@ public class JcrContent extends AbstractContent {
 
                @Override
                public Content next() {
-                       current = new JcrContent(contentSession, nodeIterator.nextNode());
+                       current = new JcrContent(session, provider, nodeIterator.nextNode());
                        return current;
                }
 
@@ -128,14 +130,14 @@ public class JcrContent extends AbstractContent {
 
        @Override
        public Content getParent() {
-               return new JcrContent(contentProvider, Jcr.getParent(getJcrNode()));
+               return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
        }
 
        @Override
-       public Content add(String name, ContentName... classes) {
+       public Content add(QName name, QName... classes) {
                if (classes.length > 0) {
-                       ContentName primaryType = classes[0];
-                       Node child = Jcr.addNode(getJcrNode(), name, primaryType.toString());
+                       QName primaryType = classes[0];
+                       Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
                        for (int i = 1; i < classes.length; i++) {
                                try {
                                        child.addMixin(classes[i].toString());
@@ -145,7 +147,7 @@ public class JcrContent extends AbstractContent {
                        }
 
                } else {
-                       Jcr.addNode(getJcrNode(), name, NodeType.NT_UNSTRUCTURED);
+                       Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
                }
                return null;
        }
@@ -156,8 +158,8 @@ public class JcrContent extends AbstractContent {
        }
 
        @Override
-       protected void removeAttr(String key) {
-               Property property = Jcr.getProperty(getJcrNode(), key);
+       protected void removeAttr(QName key) {
+               Property property = Jcr.getProperty(getJcrNode(), key.toString());
                if (property != null) {
                        try {
                                property.remove();
@@ -168,7 +170,7 @@ public class JcrContent extends AbstractContent {
 
        }
 
-       static class JcrKeyIterator implements Iterator<String> {
+       class JcrKeyIterator implements Iterator<QName> {
                private final JcrContentProvider contentSession;
                private final PropertyIterator propertyIterator;
 
@@ -183,12 +185,12 @@ public class JcrContent extends AbstractContent {
                }
 
                @Override
-               public String next() {
+               public QName next() {
                        Property property = null;
                        try {
                                property = propertyIterator.nextProperty();
                                // TODO map standard property names
-                               return property.getName();
+                               return session.parsePrefixedName(property.getName());
                        } catch (RepositoryException e) {
                                throw new JcrException("Cannot retrieve property " + property, null);
                        }
index 5e4a1b337133bee93ae36e859cba152dbea0c56f..761c087f0f9551732b57c01e12e6ce36097b11e1 100644 (file)
@@ -1,18 +1,30 @@
 package org.argeo.cms.jcr.gcr;
 
+import java.util.Arrays;
+import java.util.Iterator;
+
 import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.xml.namespace.NamespaceContext;
 
 import org.argeo.api.gcr.Content;
 import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
 
-public class JcrContentProvider implements ContentProvider {
+public class JcrContentProvider implements ContentProvider, NamespaceContext {
        private Repository jcrRepository;
-       private Session jcrSession;
+       private Session adminSession;
 
        public void init() {
-               jcrSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
+               adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
+       }
+
+       public void destroy() {
+               JcrUtils.logoutQuietly(adminSession);
        }
 
        public void setJcrRepository(Repository jcrRepository) {
@@ -20,15 +32,39 @@ public class JcrContentProvider implements ContentProvider {
        }
 
        @Override
-       public Content get() {
+       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+               // TODO Auto-generated method stub
                return null;
        }
 
+       /*
+        * NAMESPACE CONTEXT
+        */
        @Override
-       public Content get(String relativePath) {
-               // TODO Auto-generated method stub
-               return null;
+       public String getNamespaceURI(String prefix) {
+               try {
+                       return adminSession.getNamespaceURI(prefix);
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       @Override
+       public String getPrefix(String namespaceURI) {
+               try {
+                       return adminSession.getNamespacePrefix(namespaceURI);
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               try {
+                       return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator();
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
        }
 
-       
 }
index 092a35739a6fb9c2c630a4501195273c54def0e6..ba6d7b477eca446a2e5b9625704e01fda5e6b9a0 100644 (file)
@@ -3,6 +3,8 @@ package org.argeo.cms.swt.gcr;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
+import javax.xml.namespace.QName;
+
 import org.argeo.api.gcr.Content;
 import org.argeo.cms.gcr.fs.FsContentProvider;
 import org.argeo.cms.swt.CmsSwtUtils;
@@ -36,7 +38,7 @@ public class GcrContentTreeView extends Composite {
                treeGd.widthHint = 300;
                tree.setLayoutData(treeGd);
                initTable();
-               
+
                table.setLayoutData(CmsSwtUtils.fillAll());
        }
 
@@ -44,7 +46,7 @@ public class GcrContentTreeView extends Composite {
                tree = new Tree(this, 0);
                for (Content c : rootContent) {
                        TreeItem root = new TreeItem(tree, 0);
-                       root.setText(c.getName());
+                       root.setText(c.getName().toString());
                        root.setData(c);
                        new TreeItem(root, 0);
                }
@@ -59,7 +61,7 @@ public class GcrContentTreeView extends Composite {
                        Content content = (Content) root.getData();
                        for (Content c : content) {
                                TreeItem item = new TreeItem(root, 0);
-                               item.setText(c.getName());
+                               item.setText(c.getName().toString());
                                item.setData(c);
                                boolean hasChildren = true;
                                if (hasChildren) {
@@ -91,9 +93,9 @@ public class GcrContentTreeView extends Composite {
                for (TableItem item : table.getItems()) {
                        item.dispose();
                }
-               for (String key : selected.keySet()) {
+               for (QName key : selected.keySet()) {
                        TableItem item = new TableItem(table, 0);
-                       item.setText(0, key);
+                       item.setText(0, key.toString());
                        Object value = selected.get(key);
                        item.setText(1, value.toString());
                }
@@ -115,7 +117,7 @@ public class GcrContentTreeView extends Composite {
                shell.setLayout(new FillLayout());
 
                FsContentProvider contentSession = new FsContentProvider(basePath);
-               GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get());
+//             GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
 
                shell.setSize(shell.computeSize(800, 600));
                shell.open();
index 0baae356ca2319dcdda0e8a958c0e732476c2628..69f2e94d4f60dca6565993c19254748747156a3e 100644 (file)
@@ -4,19 +4,45 @@ import java.security.AccessController;
 import java.util.Locale;
 import java.util.Map;
 import java.util.NavigableMap;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.stream.Collectors;
 
 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.CrName;
 import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.api.gcr.spi.ProvidedRepository;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 
-public class CmsContentRepository implements ContentRepository {
+public class CmsContentRepository implements ProvidedRepository {
        private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
 
+       // TODO synchronize ?
+       private NavigableMap<String, String> prefixes = new TreeMap<>();
+
+       public CmsContentRepository() {
+               prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
+               prefixes.put("basic", CrName.CR_NAMESPACE_URI);
+               prefixes.put("owner", CrName.CR_NAMESPACE_URI);
+               prefixes.put("posix", CrName.CR_NAMESPACE_URI);
+       }
+
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+
+       /*
+        * REPOSITORY
+        */
+
        @Override
        public ContentSession get() {
                return get(CmsContextImpl.getCmsContext().getDefaultLocale());
@@ -32,7 +58,26 @@ public class CmsContentRepository implements ContentRepository {
                partitions.put(base, provider);
        }
 
-       class CmsContentSession implements ContentSession {
+       public void registerPrefix(String prefix, String namespaceURI) {
+               String registeredUri = prefixes.get(prefix);
+               if (registeredUri == null) {
+                       prefixes.put(prefix, namespaceURI);
+                       return;
+               }
+               if (!registeredUri.equals(namespaceURI))
+                       throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
+               // do nothing if same namespace is already registered
+       }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       /*
+        * SESSION
+        */
+
+       class CmsContentSession implements ProvidedSession {
                private Subject subject;
                private Locale locale;
 
@@ -43,9 +88,11 @@ public class CmsContentRepository implements ContentRepository {
 
                @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);
+                       Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
+                       String mountPath = entry.getKey();
+                       ContentProvider provider = entry.getValue();
+                       String relativePath = path.substring(mountPath.length());
+                       return provider.get(CmsContentSession.this, mountPath, relativePath);
                }
 
                @Override
@@ -58,6 +105,35 @@ public class CmsContentRepository implements ContentRepository {
                        return locale;
                }
 
+               @Override
+               public ProvidedRepository getRepository() {
+                       return CmsContentRepository.this;
+               }
+
+               /*
+                * NAMESPACE CONTEXT
+                */
+
+               @Override
+               public String findNamespace(String prefix) {
+                       return prefixes.get(prefix);
+               }
+
+               @Override
+               public Set<String> findPrefixes(String namespaceURI) {
+                       Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
+                                       .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
+
+                       return res;
+               }
+
+               @Override
+               public String findPrefix(String namespaceURI) {
+                       if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
+                               return CrName.CR_DEFAULT_PREFIX;
+                       return ProvidedSession.super.findPrefix(namespaceURI);
+               }
+
        }
 
 }
index 62ae613f0f737b4c71befa8bee527daf1f59e9d9..5efa65959b50a86affbf5e0b3e0b5bcc246c339d 100644 (file)
@@ -8,41 +8,62 @@ import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.time.Instant;
-import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Set;
 
+import javax.xml.namespace.QName;
+
 import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentName;
 import org.argeo.api.gcr.ContentResourceException;
 import org.argeo.api.gcr.CrName;
-import org.argeo.api.gcr.ContentName;
 import org.argeo.api.gcr.spi.AbstractContent;
+import org.argeo.api.gcr.spi.ProvidedContent;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.argeo.util.FsUtils;
 
-public class FsContent extends AbstractContent implements Content {
+public class FsContent extends AbstractContent implements ProvidedContent {
        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;
+       private static final Map<QName, String> BASIC_KEYS;
+       private static final Map<QName, String> POSIX_KEYS;
        static {
-               POSIX_KEYS = new HashSet<>(BASIC_KEYS);
-               POSIX_KEYS.add("owner:owner");
-               POSIX_KEYS.add("posix:group");
-               POSIX_KEYS.add("posix:permissions");
+               BASIC_KEYS = new HashMap<>();
+               BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
+               BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
+               BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
+               BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+
+               POSIX_KEYS = new HashMap<>(BASIC_KEYS);
+               POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
+               POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
+               POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
        }
 
-       private final FsContentProvider contentProvider;
+       private final ProvidedSession session;
+       private final FsContentProvider provider;
        private final Path path;
        private final boolean isRoot;
+       private final QName name;
 
-       public FsContent(FsContentProvider contentProvider, Path path) {
-               super();
-               this.contentProvider = contentProvider;
+       protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
+               this.session = session;
+               this.provider = contentProvider;
                this.path = path;
                this.isRoot = contentProvider.isRoot(path);
+               // TODO check file names with ':' ?
+               if (isRoot)
+                       this.name = CrName.ROOT.get();
+               else
+                       this.name = session.parsePrefixedName(path.getFileName().toString());
+       }
+
+       protected FsContent(FsContent context, Path path) {
+               this(context.getSession(), context.getProvider(), path);
        }
 
        private boolean isPosix() {
@@ -50,10 +71,8 @@ public class FsContent extends AbstractContent implements Content {
        }
 
        @Override
-       public String getName() {
-               if (isRoot)
-                       return "";
-               return path.getFileName().toString();
+       public QName getName() {
+               return name;
        }
 
        /*
@@ -61,7 +80,7 @@ public class FsContent extends AbstractContent implements Content {
         */
 
        @Override
-       public <A> A get(String key, Class<A> clss) {
+       public <A> A get(QName key, Class<A> clss) {
                Object value;
                try {
                        // We need to add user: when accessing via Files#getAttribute
@@ -85,13 +104,13 @@ public class FsContent extends AbstractContent implements Content {
        }
 
        @Override
-       protected Iterable<String> keys() {
-               Set<String> result = new HashSet<>(isPosix() ? POSIX_KEYS : BASIC_KEYS);
+       protected Iterable<QName> keys() {
+               Set<QName> result = new HashSet<>(isPosix() ? POSIX_KEYS.keySet() : BASIC_KEYS.keySet());
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                if (udfav != null) {
                        try {
                                for (String name : udfav.list()) {
-                                       result.add(name);
+                                       result.add(session.parsePrefixedName(name));
                                }
                        } catch (IOException e) {
                                throw new ContentResourceException("Cannot list attributes for " + path, e);
@@ -101,33 +120,33 @@ public class FsContent extends AbstractContent implements Content {
        }
 
        @Override
-       protected void removeAttr(String key) {
+       protected void removeAttr(QName key) {
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                try {
-                       udfav.delete(key);
+                       udfav.delete(session.toPrefixedName(key));
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
        }
 
        @Override
-       public Object put(String key, Object value) {
+       public Object put(QName 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);
+                       int size = udfav.write(session.toPrefixedName(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;
+       protected String toFsAttributeKey(QName key) {
+               if (POSIX_KEYS.containsKey(key))
+                       return POSIX_KEYS.get(key);
                else
-                       return USER_ + key;
+                       return USER_ + session.toPrefixedName(key);
        }
 
        /*
@@ -137,7 +156,7 @@ public class FsContent extends AbstractContent implements Content {
        public Iterator<Content> iterator() {
                if (Files.isDirectory(path)) {
                        try {
-                               return Files.list(path).map((p) -> (Content) new FsContent(contentProvider, p)).iterator();
+                               return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
                        } catch (IOException e) {
                                throw new ContentResourceException("Cannot list " + path, e);
                        }
@@ -147,10 +166,10 @@ public class FsContent extends AbstractContent implements Content {
        }
 
        @Override
-       public Content add(String name, ContentName... classes) {
+       public Content add(QName name, QName... classes) {
                try {
-                       Path newPath = path.resolve(name);
-                       if (ContentName.contains(classes, CrName.COLLECTION))
+                       Path newPath = path.resolve(session.toPrefixedName(name));
+                       if (ContentName.contains(classes, CrName.COLLECTION.get()))
                                Files.createDirectory(newPath);
                        else
                                Files.createFile(newPath);
@@ -158,7 +177,7 @@ public class FsContent extends AbstractContent implements Content {
 //             for(ContentClass clss:classes) {
 //                     Files.setAttribute(newPath, name, newPath, null)
 //             }
-                       return new FsContent(contentProvider, newPath);
+                       return new FsContent(this, newPath);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot create new content", e);
                }
@@ -173,7 +192,20 @@ public class FsContent extends AbstractContent implements Content {
        public Content getParent() {
                if (isRoot)
                        return null;// TODO deal with mounts
-               return new FsContent(contentProvider, path.getParent());
+               return new FsContent(this, path.getParent());
+       }
+
+       /*
+        * ACCESSORS
+        */
+       @Override
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       @Override
+       public FsContentProvider getProvider() {
+               return provider;
        }
 
 }
index d15f9c32b4c5ddef225e527b88e11ff97bfcb9d4..e5732da1ecc7ac143127f94d5ec6ea7c9cb0fd44 100644 (file)
@@ -7,6 +7,7 @@ import java.nio.file.Path;
 import org.argeo.api.gcr.Content;
 import org.argeo.api.gcr.ContentResourceException;
 import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.api.gcr.spi.ProvidedSession;
 
 public class FsContentProvider implements ContentProvider {
        private final Path rootPath;
@@ -25,12 +26,7 @@ public class FsContentProvider implements ContentProvider {
        }
 
        @Override
-       public Content get() {
-               return new FsContent(this, rootPath);
-       }
-
-       @Override
-       public Content get(String relativePath) {
-               return new FsContent(this, rootPath.resolve(relativePath));
+       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+               return new FsContent(session, this, rootPath.resolve(relativePath));
        }
 }
index 30d590df4f48720768ea011cb55a3cf5c50f5452..57e6ba7cecea5974af10b9776a98caa58d6a7458 100644 (file)
@@ -4,57 +4,105 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
 import org.argeo.api.gcr.Content;
 import org.argeo.api.gcr.ContentName;
 import org.argeo.api.gcr.spi.AbstractContent;
+import org.argeo.api.gcr.spi.ProvidedContent;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.Text;
 
-public class DomContent extends AbstractContent implements Content {
+public class DomContent extends AbstractContent implements ProvidedContent {
 
-       private final DomContentProvider contentProvider;
+       private final ProvidedSession session;
+       private final DomContentProvider provider;
        private final Element element;
 
 //     private String text = null;
        private Boolean hasText = null;
 
-       public DomContent(DomContentProvider contentProvider, Element element) {
-               this.contentProvider = contentProvider;
+       public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
+               this.session = session;
+               this.provider = contentProvider;
                this.element = element;
        }
 
-       @Override
-       public Iterator<Content> iterator() {
-               NodeList nodeList = element.getChildNodes();
-               return new ElementIterator(contentProvider, nodeList);
+       public DomContent(DomContent context, Element element) {
+               this(context.getSession(), context.getProvider(), element);
        }
 
        @Override
-       public String getName() {
-               return element.getNodeName();
+       public QName getName() {
+               return toQName(this.element);
+       }
+
+       protected QName toQName(Node node) {
+               String prefix = node.getPrefix();
+               if (prefix == null) {
+                       String namespaceURI = node.getNamespaceURI();
+                       if (namespaceURI == null)
+                               namespaceURI = node.getOwnerDocument().lookupNamespaceURI(null);
+                       if (namespaceURI == null) {
+                               return toQName(node, node.getLocalName());
+                       } else {
+                               String contextPrefix = session.getPrefix(namespaceURI);
+                               if (contextPrefix == null)
+                                       throw new IllegalStateException("Namespace " + namespaceURI + " is unbound");
+                               return toQName(node, namespaceURI, node.getLocalName(), session);
+                       }
+               } else {
+                       String namespaceURI = node.getNamespaceURI();
+                       if (namespaceURI == null)
+                               namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix);
+                       if (namespaceURI == null) {
+                               namespaceURI = session.getNamespaceURI(prefix);
+                               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                                       throw new IllegalStateException("Prefix " + prefix + " is unbound");
+                               // TODO bind the prefix in the document?
+                       }
+                       return toQName(node, namespaceURI, node.getLocalName(), session);
+               }
+       }
+
+       protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
+               return new ContentName(namespaceURI, localName, session);
+       }
+
+       protected QName toQName(Node source, String localName) {
+               return new ContentName(localName);
        }
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
 
        @Override
-       public Iterable<String> keys() {
+       public Iterable<QName> keys() {
                // TODO implement an iterator?
-               Set<String> result = new HashSet<>();
+               Set<QName> result = new HashSet<>();
                NamedNodeMap attributes = element.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                        Attr attr = (Attr) attributes.item(i);
-                       String attrName = attr.getNodeName();
-                       result.add(attrName);
+                       QName key = toQName(attr);
+                       result.add(key);
                }
                return result;
        }
 
        @Override
-       public <A> A get(String key, Class<A> clss) {
-               if (element.hasAttribute(key)) {
-                       String value = element.getAttribute(key);
+       public <A> A get(QName key, Class<A> clss) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               if (element.hasAttributeNS(namespaceUriOrNull, key.getLocalPart())) {
+                       String value = element.getAttributeNS(namespaceUriOrNull, key.getLocalPart());
                        if (clss.isAssignableFrom(String.class))
                                return (A) value;
                        else
@@ -63,6 +111,17 @@ public class DomContent extends AbstractContent implements Content {
                        return null;
        }
 
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               element.setAttributeNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+                               value.toString());
+               return previous;
+       }
+
        @Override
        public boolean hasText() {
 //             return element instanceof Text;
@@ -100,6 +159,16 @@ public class DomContent extends AbstractContent implements Content {
                        return null;
        }
 
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public Iterator<Content> iterator() {
+               NodeList nodeList = element.getChildNodes();
+               return new ElementIterator(session, provider, nodeList);
+       }
+
        @Override
        public Content getParent() {
                Node parent = element.getParentNode();
@@ -107,14 +176,19 @@ public class DomContent extends AbstractContent implements Content {
                        return null;
                if (!(parent instanceof Element))
                        throw new IllegalStateException("Parent is not an element");
-               return new DomContent(contentProvider, (Element) parent);
+               return new DomContent(this, (Element) parent);
        }
 
        @Override
-       public Content add(String name, ContentName... classes) {
+       public Content add(QName name, QName... classes) {
                // TODO consider classes
-               Element child = contentProvider.createElement(name);
-               return new DomContent(contentProvider, child);
+               Document document = this.element.getOwnerDocument();
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+                               : name.getNamespaceURI();
+               Element child = document.createElementNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+               element.appendChild(child);
+               return new DomContent(this, child);
        }
 
        @Override
@@ -125,9 +199,20 @@ public class DomContent extends AbstractContent implements Content {
        }
 
        @Override
-       protected void removeAttr(String key) {
-               element.removeAttribute(key);
+       protected void removeAttr(QName key) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               element.removeAttributeNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart());
+
+       }
+
+       public ProvidedSession getSession() {
+               return session;
+       }
 
+       public DomContentProvider getProvider() {
+               return provider;
        }
 
 }
index d1a625e32af960c3cde8d450141b33245fc63672..dc200bf3db48c10258612379efffd47f76f18c0a 100644 (file)
@@ -1,61 +1,99 @@
 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
 
 import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentUtils;
+import org.argeo.api.gcr.ContentNotFoundException;
 import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
 
-public class DomContentProvider implements ContentProvider {
+public class DomContentProvider implements ContentProvider, NamespaceContext {
        private Document document;
 
+       // XPath
+       // TODO centralise in some executor?
+       private final ThreadLocal<XPath> xPath;
+
        public DomContentProvider(Document document) {
                this.document = document;
                this.document.normalizeDocument();
+               XPathFactory xPathFactory = XPathFactory.newInstance();
+               xPath = new ThreadLocal<>() {
+
+                       @Override
+                       protected XPath initialValue() {
+                               // TODO set the document as namespace context?
+                               XPath res= xPathFactory.newXPath();
+                               res.setNamespaceContext(DomContentProvider.this);
+                               return res;
+                       }
+               };
        }
 
+//     @Override
+//     public Content get() {
+//             return new DomContent(this, document.getDocumentElement());
+//     }
+
+//     public Element createElement(String name) {
+//             return document.createElementNS(null, name);
+//
+//     }
+
        @Override
-       public Content get() {
-               return new DomContent(this, document.getDocumentElement());
-       }
+       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+               if ("".equals(relativePath))
+                       return new DomContent(session, this, document.getDocumentElement());
+               if (relativePath.startsWith("/"))
+                       throw new IllegalArgumentException("Relative path cannot start with /");
 
-       public Element createElement(String name) {
-               return document.createElement(name);
+               String xPathExpression = '/' + relativePath;
+               if ("/".equals(mountPath))
+                       xPathExpression = "/cr:root" + xPathExpression;
+               try {
+                       NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
+                       if (nodes.getLength() > 1)
+                               throw new IllegalArgumentException(
+                                               "Multiple content found for " + relativePath + " under " + mountPath);
+                       if (nodes.getLength() == 0)
+                               throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found");
+                       Element element = (Element) nodes.item(0);
+                       return new DomContent(session, this, element);
+               } catch (XPathExpressionException e) {
+                       throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e);
+               }
        }
 
+       /*
+        * NAMESPACE CONTEXT
+        */
        @Override
-       public Content get(String relativePath) {
-               // TODO Auto-generated method stub
-               return null;
+       public String getNamespaceURI(String prefix) {
+               return document.lookupNamespaceURI(prefix);
        }
 
-       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));
+       @Override
+       public String getPrefix(String namespaceURI) {
+               return document.lookupPrefix(namespaceURI);
+       }
 
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               List<String> res = new ArrayList<>();
+               res.add(getPrefix(namespaceURI));
+               return Collections.unmodifiableList(res).iterator();
        }
+
 }
index e3efcb4b3f9d620a9b3553be29b045902a5ef900..db25b653d85f9c33f8f2d9e6b5de0f8d3a5a659f 100644 (file)
@@ -4,20 +4,23 @@ import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.spi.ProvidedSession;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
-public class ElementIterator implements Iterator<Content> {
-       private final DomContentProvider contentSession;
+class ElementIterator implements Iterator<Content> {
+       private final ProvidedSession session;
+       private final DomContentProvider provider;
        private final NodeList nodeList;
 
        private int currentIndex;
        private final int length;
        private Element nextElement = null;
 
-       public ElementIterator(DomContentProvider contentSession, NodeList nodeList) {
-               this.contentSession = contentSession;
+       public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+               this.session = session;
+               this.provider = provider;
                this.nodeList = nodeList;
 
                this.length = nodeList.getLength();
@@ -45,7 +48,7 @@ public class ElementIterator implements Iterator<Content> {
        public Content next() {
                if (nextElement == null)
                        throw new NoSuchElementException();
-               DomContent result = new DomContent(contentSession, nextElement);
+               DomContent result = new DomContent(session, provider, nextElement);
                currentIndex++;
                nextElement = findNext();
                return result;