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)) {
}
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);
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());
+ }
}
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();
* 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();
/*
* 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
+++ /dev/null
-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;
- }
-}
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;
--- /dev/null
+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;
+ }
+
+}
+++ /dev/null
-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);
- }
-}
--- /dev/null
+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);
+ }
+
+
+}
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();
+ }
+
}
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);
}
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) {
--- /dev/null
+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;
+ }
+
+ }
+
+}
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;
}
}
+++ /dev/null
-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;
- }
-
- }
-
-}
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>() {
// };
// 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() {
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();
@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() {
}
@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;
}
@Override
public int size() {
int count = 0;
- for (String key : keys()) {
+ for (QName key : keys()) {
count++;
}
return count;
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);
}
--- /dev/null
+package org.argeo.api.gcr.spi;
+
+import org.argeo.api.gcr.Content;
+
+public interface ProvidedContent extends Content {
+ ProvidedSession getSession();
+
+ ContentProvider getProvider();
+}
--- /dev/null
+package org.argeo.api.gcr.spi;
+
+import org.argeo.api.gcr.ContentRepository;
+
+public interface ProvidedRepository extends ContentRepository {
+}
--- /dev/null
+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();
+ }
+
+}
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);
}
}
}
- 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;
}
@Override
public Content next() {
- current = new JcrContent(contentSession, nodeIterator.nextNode());
+ current = new JcrContent(session, provider, nodeIterator.nextNode());
return current;
}
@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());
}
} else {
- Jcr.addNode(getJcrNode(), name, NodeType.NT_UNSTRUCTURED);
+ Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
}
return null;
}
}
@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();
}
- static class JcrKeyIterator implements Iterator<String> {
+ class JcrKeyIterator implements Iterator<QName> {
private final JcrContentProvider contentSession;
private final PropertyIterator propertyIterator;
}
@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);
}
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) {
}
@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);
+ }
}
-
}
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;
treeGd.widthHint = 300;
tree.setLayoutData(treeGd);
initTable();
-
+
table.setLayoutData(CmsSwtUtils.fillAll());
}
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);
}
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) {
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());
}
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();
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());
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;
@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
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);
+ }
+
}
}
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() {
}
@Override
- public String getName() {
- if (isRoot)
- return "";
- return path.getFileName().toString();
+ public QName getName() {
+ return name;
}
/*
*/
@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
}
@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);
}
@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);
}
/*
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);
}
}
@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);
// 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);
}
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;
}
}
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;
}
@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));
}
}
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
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;
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();
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
}
@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;
}
}
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();
}
+
}
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();
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;