<artifactId>org.argeo.util</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
- <!-- <dependency> -->
- <!-- <groupId>org.argeo.commons</groupId> -->
- <!-- <artifactId>org.argeo.jcr</artifactId> -->
- <!-- <version>2.3-SNAPSHOT</version> -->
- <!-- </dependency> -->
- <!-- <dependency> -->
- <!-- <groupId>org.argeo.commons</groupId> -->
- <!-- <artifactId>org.argeo.core</artifactId> -->
- <!-- <version>2.3-SNAPSHOT</version> -->
- <!-- </dependency> -->
<dependency>
<groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.api</artifactId>
+ <artifactId>org.argeo.api.acr</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.api.cms</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.api.acr</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=11
--- /dev/null
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
--- /dev/null
+Import-Package: \
+javax.security.*
+
+Export-Package: org.argeo.api.acr.*
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>argeo-commons</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.api.acr</artifactId>
+ <name>ACR API</name>
+ <packaging>jar</packaging>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.api.acr;
+
+/**
+ * An attribute type MUST consistently parse a string to an object so that
+ * <code>parse(obj.toString()).equals(obj)</code> is verified.
+ * {@link #format(Object)} can be overridden to provide more efficient
+ * implementations but the returned
+ * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
+ * is verified.
+ */
+public interface AttributeFormatter<T> {
+ /** Parses a String to a Java object. */
+ T parse(String str) throws IllegalArgumentException;
+
+ /** Default implementation returns {@link Object#toString()} on the argument. */
+ default String format(T obj) {
+ return obj.toString();
+ }
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/** A name that can be expressed with various conventions. */
+public class CompositeString {
+ public final static Character UNDERSCORE = Character.valueOf('_');
+ public final static Character SPACE = Character.valueOf(' ');
+ public final static Character DASH = Character.valueOf('-');
+
+ private final String[] parts;
+
+ // optimisation
+ private final int hashCode;
+
+ public CompositeString(String str) {
+ Objects.requireNonNull(str, "String cannot be null");
+ if ("".equals(str.trim()))
+ throw new IllegalArgumentException("String cannot be empty");
+ if (!str.equals(str.trim()))
+ throw new IllegalArgumentException("String must be trimmed");
+ this.parts = toParts(str);
+ hashCode = hashCode(this.parts);
+ }
+
+ public String toString(char separator, boolean upperCase) {
+ StringBuilder sb = null;
+ for (String part : parts) {
+ if (sb == null) {
+ sb = new StringBuilder();
+ } else {
+ sb.append(separator);
+ }
+ sb.append(upperCase ? part.toUpperCase() : part);
+ }
+ return sb.toString();
+ }
+
+ public String toStringCaml(boolean firstCharUpperCase) {
+ StringBuilder sb = null;
+ for (String part : parts) {
+ if (sb == null) {// first
+ sb = new StringBuilder();
+ sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
+ } else {
+ sb.append(Character.toUpperCase(part.charAt(0)));
+ }
+
+ if (part.length() > 1)
+ sb.append(part.substring(1));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof CompositeString))
+ return false;
+
+ CompositeString other = (CompositeString) obj;
+ return Arrays.equals(parts, other.parts);
+ }
+
+ @Override
+ public String toString() {
+ return toString(DASH, false);
+ }
+
+ public static String[] toParts(String str) {
+ Character separator = null;
+ if (str.indexOf(UNDERSCORE) >= 0) {
+ checkNo(str, SPACE);
+ checkNo(str, DASH);
+ separator = UNDERSCORE;
+ } else if (str.indexOf(DASH) >= 0) {
+ checkNo(str, SPACE);
+ checkNo(str, UNDERSCORE);
+ separator = DASH;
+ } else if (str.indexOf(SPACE) >= 0) {
+ checkNo(str, DASH);
+ checkNo(str, UNDERSCORE);
+ separator = SPACE;
+ }
+
+ List<String> res = new ArrayList<>();
+ if (separator != null) {
+ StringTokenizer st = new StringTokenizer(str, separator.toString());
+ while (st.hasMoreTokens()) {
+ res.add(st.nextToken().toLowerCase());
+ }
+ } 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)) {
+ if (current != null)
+ res.add(current.toString());
+ current = new StringBuilder();
+ }
+ if (current == null)// first char is lower case
+ current = new StringBuilder();
+ current.append(Character.toLowerCase(c));
+ }
+ res.add(current.toString());
+ }
+ return res.toArray(new String[res.size()]);
+ }
+
+ private static void checkNo(String str, Character c) {
+ if (str.indexOf(c) >= 0) {
+ throw new IllegalArgumentException("Only one kind of sperator is allowed");
+ }
+ }
+
+ private static int hashCode(String[] parts) {
+ int hashCode = 0;
+ for (String part : parts) {
+ hashCode = hashCode + part.hashCode();
+ }
+ return hashCode;
+ }
+
+ 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 new CompositeString("MY_COMPLEX_NAME").equals(camlName);
+ assert new CompositeString("My complex Name").equals(camlName);
+ assert new CompositeString("MyComplexName").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));
+
+ return CompositeString.class.desiredAssertionStatus();
+ }
+
+ public static void main(String[] args) {
+ System.out.println(smokeTests());
+ }
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+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<QName, Object> {
+
+ QName getName();
+
+ String getPath();
+
+ Content getParent();
+
+ /*
+ * ATTRIBUTES OPERATIONS
+ */
+
+ <A> Optional<A> get(QName key, Class<A> clss);
+
+ 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));
+ }
+
+ Class<?> getType(QName key);
+
+ boolean isMultiple(QName key);
+
+ <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
+
+ default <A> List<A> getMultiple(QName key) {
+ Class<A> type;
+ try {
+ type = (Class<A>) getType(key);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException("Requested type is not the default type");
+ }
+ Optional<List<A>> res = getMultiple(key, type);
+ if (res == null)
+ return null;
+ else {
+ if (res.isEmpty())
+ throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
+ return res.get();
+ }
+ }
+
+ /*
+ * CONTENT OPERATIONS
+ */
+ 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();
+
+ /*
+ * DEFAULT METHODS
+ */
+ default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+ throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
+ }
+
+ default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
+ throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
+ }
+
+ /*
+ * 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);
+// }
+
+ /*
+ * EXPERIMENTAL UNSUPPORTED
+ */
+ default boolean hasText() {
+ return false;
+ }
+
+ default String getText() {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+/** When a feature is not supported by the underlying repository. */
+public class ContentFeatureUnsupportedException extends UnsupportedOperationException {
+ private static final long serialVersionUID = 3193936026343114949L;
+
+ public ContentFeatureUnsupportedException() {
+ }
+
+ public ContentFeatureUnsupportedException(String message) {
+ super(message);
+ }
+
+ public ContentFeatureUnsupportedException(Throwable cause) {
+ super(cause);
+ }
+
+ public ContentFeatureUnsupportedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+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;
+
+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) {
+ super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
+ }
+
+ private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
+ Objects.requireNonNull(nsContext, "Namespace context cannot be null");
+ Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+ String prefix = nsContext.getNamespaceURI(namespaceURI);
+ if (prefix == null)
+ throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
+ return prefix;
+ }
+
+ ContentName(String namespaceURI, String localPart, String prefix) {
+ super(namespaceURI, localPart, prefix);
+ }
+
+ public ContentName(String localPart) {
+ super(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());
+ }
+
+ 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);
+ }
+
+ /*
+ * UTILITIES
+ */
+
+ 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.acr;
+
+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();
+
+ String getNamespaceURI();
+
+ String getDefaultPrefix();
+
+ @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.acr;
+
+/** 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);
+ }
+
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.util.Locale;
+import java.util.function.Supplier;
+
+/**
+ * A content repository is an actually running implementation of various kind of
+ * content system. It allows a pre-authenticated caller to open a session.
+ */
+public interface ContentRepository extends Supplier<ContentSession> {
+ ContentSession get(Locale locale);
+}
--- /dev/null
+package org.argeo.api.acr;
+
+/**
+ * When there is a problem the underlying resources, typically IO, network, DB
+ * access, etc.
+ */
+public class ContentResourceException extends IllegalStateException {
+ private static final long serialVersionUID = -2850145213683756996L;
+
+ public ContentResourceException() {
+ }
+
+ public ContentResourceException(String s) {
+ super(s);
+ }
+
+ public ContentResourceException(Throwable cause) {
+ super(cause);
+ }
+
+ public ContentResourceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+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 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();
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+public interface ContentStore {
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.io.PrintStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import javax.xml.namespace.QName;
+
+public class ContentUtils {
+ public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
+ traverse(content, doIt, 0);
+ }
+
+ public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
+ doIt.accept(content, currentDepth);
+ int nextDepth = currentDepth + 1;
+ for (Content child : content) {
+ traverse(child, doIt, nextDepth);
+ }
+ }
+
+ public static void print(Content content, PrintStream out, int depth, boolean printText) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ sb.append(" ");
+ }
+ String prefix = sb.toString();
+ out.println(prefix + content.getName());
+ for (QName key : content.keySet()) {
+ out.println(prefix + " " + key + "=" + content.get(key));
+ }
+ if (printText) {
+ if (content.hasText()) {
+ out.println("<![CDATA[" + content.getText().trim() + "]]>");
+ }
+ }
+ }
+
+ public static URI bytesToDataURI(byte[] arr) {
+ String base64Str = Base64.getEncoder().encodeToString(arr);
+ try {
+ final String PREFIX = "data:application/octet-stream;base64,";
+ return new URI(PREFIX + base64Str);
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
+ }
+
+ }
+
+ public static byte[] bytesFromDataURI(URI uri) {
+ if (!"data".equals(uri.getScheme()))
+ throw new IllegalArgumentException("URI must have 'data' as a scheme");
+ String schemeSpecificPart = uri.getSchemeSpecificPart();
+ int commaIndex = schemeSpecificPart.indexOf(',');
+ String prefix = schemeSpecificPart.substring(0, commaIndex);
+ List<String> info = Arrays.asList(prefix.split(";"));
+ if (!info.contains("base64"))
+ throw new IllegalArgumentException("URI must specify base64");
+
+ String base64Str = schemeSpecificPart.substring(commaIndex + 1);
+ return Base64.getDecoder().decode(base64Str);
+
+ }
+
+ public static <T> boolean isString(T t) {
+ return t instanceof String;
+ }
+
+ /** Singleton. */
+ private ContentUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.UUID;
+
+import javax.xml.XMLConstants;
+
+/**
+ * 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 implements ContentNameSupplier {
+ 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)
+ DATE_TIME(Instant.class, new InstantFormatter()), //
+ UUID(UUID.class, new UuidFormatter()), //
+ ANY_URI(URI.class, new UriFormatter()), //
+ STRING(String.class, new StringFormatter()), //
+ ;
+
+ private final Class<?> clss;
+ private final AttributeFormatter<?> formatter;
+
+ private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+ this.clss = clss;
+ this.formatter = formatter;
+ }
+
+ public Class<?> getClss() {
+ return clss;
+ }
+
+ public AttributeFormatter<?> getFormatter() {
+ return formatter;
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ if (equals(UUID))
+ return CrName.CR_DEFAULT_PREFIX;
+ else
+ return "xs";
+ }
+
+ @Override
+ public String getNamespaceURI() {
+ if (equals(UUID))
+ return CrName.CR_NAMESPACE_URI;
+ else
+ return XMLConstants.W3C_XML_SCHEMA_NS_URI;
+ }
+
+ 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 DATE_TIME.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) ANY_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;
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+/** Standard names. */
+public enum CrName implements ContentNameSupplier {
+
+ /*
+ * TYPES
+ */
+ COLLECTION, // a collection type
+
+ /*
+ * ATTRIBUTES
+ */
+ UUID, // the UUID of a content
+
+ /*
+ * ATTRIBUTES FROM FILE SEMANTICS
+ */
+ CREATION_TIME, //
+ LAST_MODIFIED_TIME, //
+ SIZE, //
+ FILE_KEY, //
+ OWNER, //
+ GROUP, //
+ PERMISSIONS, //
+
+ /*
+ * CONTENT NAMES
+ */
+ ROOT,
+
+ //
+ ;
+
+ 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() {
+ value = toContentName();
+ }
+
+ @Override
+ public ContentName get() {
+ return value;
+ }
+
+ @Override
+ public String getNamespaceURI() {
+ return CR_NAMESPACE_URI;
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return CR_DEFAULT_PREFIX;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.fs;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent.Kind;
+import java.nio.file.WatchEvent.Modifier;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public abstract class AbstractFsPath<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
+ private final FS fs;
+ /** null for non absolute paths */
+ private final ST fileStore;
+
+ private final String[] segments;// null means root
+ private final boolean absolute;
+
+ private final String separator;
+
+ // optim
+ private final int hashCode;
+
+ public AbstractFsPath(FS filesSystem, String path) {
+ if (path == null)
+ throw new IllegalArgumentException("Path cannot be null");
+ this.fs = filesSystem;
+ this.separator = fs.getSeparator();
+ // TODO deal with both path and separator being empty strings
+ if (path.equals(separator)) {// root
+ this.segments = null;
+ this.absolute = true;
+ this.hashCode = 0;
+ this.fileStore = fs.getBaseFileStore();
+ return;
+ } else if (path.equals("")) {// empty path
+ this.segments = new String[] { "" };
+ this.absolute = false;
+ this.hashCode = "".hashCode();
+ this.fileStore = null;
+ return;
+ }
+
+ this.absolute = path.startsWith(toStringRoot());
+
+ String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
+ path.endsWith(separator) ? path.length() - separator.length() : path.length());
+ this.segments = trimmedPath.split(separator);
+ // clean up
+ for (int i = 0; i < this.segments.length; i++) {
+ this.segments[i] = cleanUpSegment(this.segments[i]);
+ }
+ this.hashCode = this.segments[this.segments.length - 1].hashCode();
+
+ this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
+ }
+
+ protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
+ this.segments = segments;
+ this.absolute = absolute;
+ this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
+ this.separator = filesSystem.getSeparator();
+// super(path, path == null ? true : absolute, filesSystem.getSeparator());
+// assert path == null ? absolute == true : true;
+ this.fs = filesSystem;
+// this.path = path;
+// this.absolute = path == null ? true : absolute;
+ if (isAbsolute() && fileStore == null)
+ throw new IllegalArgumentException("Absolute path requires a file store");
+ if (!isAbsolute() && fileStore != null)
+ throw new IllegalArgumentException("A file store should not be provided for a relative path");
+ this.fileStore = fileStore;
+ assert !(absolute && fileStore == null);
+ }
+
+ protected Path retrieve(String path) {
+ return getFileSystem().getPath(path);
+ }
+
+ @Override
+ public FS getFileSystem() {
+ return fs;
+ }
+
+ public ST getFileStore() {
+ return fileStore;
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ return absolute;
+ }
+
+ @Override
+ public URI toUri() {
+ try {
+ return new URI(fs.provider().getScheme(), toString(), null);
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot create URI for " + toString(), e);
+ }
+ }
+
+ @Override
+ public Path toAbsolutePath() {
+ if (isAbsolute())
+ return this;
+ // FIXME it doesn't seem right
+ return newInstance(getSegments(), true);
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ return this;
+ }
+
+ @Override
+ public File toFile() {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * PATH OPERATIONS
+ */
+ public final Path resolveSibling(Path other) {
+ if (other == null)
+ throw new NullPointerException();
+ Path parent = getParent();
+ return (parent == null) ? other : parent.resolve(other);
+ }
+
+ @Override
+ public final Path resolveSibling(String other) {
+ return resolveSibling(getFileSystem().getPath(other));
+ }
+
+ public final Path resolve(String other) {
+ return resolve(retrieve(other));
+ }
+
+ public boolean startsWith(Path other) {
+ return toString().startsWith(other.toString());
+ }
+
+ public boolean endsWith(Path other) {
+ return toString().endsWith(other.toString());
+ }
+
+ @Override
+ public Path normalize() {
+ // always normalized
+ return this;
+ }
+
+ @Override
+ public final Iterator<Path> iterator() {
+ return new Iterator<Path>() {
+ private int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return (i < getNameCount());
+ }
+
+ @Override
+ public Path next() {
+ if (i < getNameCount()) {
+ Path result = getName(i);
+ i++;
+ return result;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ return toString().compareTo(other.toString());
+ }
+
+ public Path resolve(Path other) {
+ AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
+ if (otherPath.isAbsolute())
+ return other;
+ String[] newPath;
+ if (isRoot()) {
+ newPath = new String[otherPath.segments.length];
+ System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
+ } else {
+ newPath = new String[segments.length + otherPath.segments.length];
+ System.arraycopy(segments, 0, newPath, 0, segments.length);
+ System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
+ }
+ if (!absolute)
+ return newInstance(newPath, absolute);
+ else {
+ return newInstance(toString(newPath));
+ }
+ }
+
+ public Path relativize(Path other) {
+ if (equals(other))
+ return newInstance("");
+ if (other.toString().startsWith(this.toString())) {
+ String p1 = toString();
+ String p2 = other.toString();
+ String relative = p2.substring(p1.length(), p2.length());
+ if (relative.charAt(0) == '/')
+ relative = relative.substring(1);
+ return newInstance(relative);
+ }
+ throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+ }
+
+ /*
+ * FACTORIES
+ */
+ protected abstract AbstractFsPath<FS, ST> newInstance(String path);
+
+ protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
+
+ /*
+ * CUSTOMISATIONS
+ */
+ protected String toStringRoot() {
+ return separator;
+ }
+
+ protected String cleanUpSegment(String segment) {
+ return segment;
+ }
+
+ protected boolean isRoot() {
+ return segments == null;
+ }
+
+ protected boolean isEmpty() {
+ return segments.length == 1 && "".equals(segments[0]);
+ }
+
+ /*
+ * PATH OPERATIONS
+ */
+ public AbstractFsPath<FS, ST> getRoot() {
+ return newInstance(toStringRoot());
+ }
+
+ public AbstractFsPath<FS, ST> getParent() {
+ if (isRoot())
+ return null;
+ // FIXME empty path?
+ if (segments.length == 1)// first level
+ return newInstance(toStringRoot());
+ String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
+ if (!absolute)
+ return newInstance(parentPath, absolute);
+ else
+ return newInstance(toString(parentPath));
+ }
+
+ public AbstractFsPath<FS, ST> getFileName() {
+ if (isRoot())
+ return null;
+ return newInstance(segments[segments.length - 1]);
+ }
+
+ public int getNameCount() {
+ if (isRoot())
+ return 0;
+ return segments.length;
+ }
+
+ public AbstractFsPath<FS, ST> getName(int index) {
+ if (isRoot())
+ return null;
+ return newInstance(segments[index]);
+ }
+
+ public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
+ if (isRoot())
+ return null;
+ String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
+ return newInstance(parentPath, false);
+ }
+
+ public boolean startsWith(String other) {
+ return toString().startsWith(other);
+ }
+
+ public boolean endsWith(String other) {
+ return toString().endsWith(other);
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected String toString(String[] path) {
+ if (isRoot())
+ return toStringRoot();
+ StringBuilder sb = new StringBuilder();
+ if (isAbsolute())
+ sb.append(separator);
+ for (int i = 0; i < path.length; i++) {
+ if (i != 0)
+ sb.append(separator);
+ sb.append(path[i]);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return toString(segments);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AbstractFsPath))
+ return false;
+ AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
+
+ if (isRoot()) {// root
+ if (other.isRoot())// root
+ return true;
+ else
+ return false;
+ } else {
+ if (other.isRoot())// root
+ return false;
+ }
+ // non root
+ if (segments.length != other.segments.length)
+ return false;
+ for (int i = 0; i < segments.length; i++) {
+ if (!segments[i].equals(other.segments[i]))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return newInstance(toString());
+ }
+
+ /*
+ * GETTERS / SETTERS
+ */
+ protected String[] getSegments() {
+ return segments;
+ }
+
+ protected String getSeparator() {
+ return separator;
+ }
+
+ /*
+ * UNSUPPORTED
+ */
+ @Override
+ public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.fs;
+
+import java.nio.file.FileStore;
+
+public abstract class AbstractFsStore extends FileStore {
+
+}
--- /dev/null
+package org.argeo.api.acr.fs;
+
+import java.nio.file.FileSystem;
+
+public abstract class AbstractFsSystem<ST extends AbstractFsStore> extends FileSystem {
+ public abstract ST getBaseFileStore();
+
+ public abstract ST getFileStore(String path);
+}
--- /dev/null
+package org.argeo.api.acr.spi;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements Content {
+
+ /*
+ * ATTRIBUTES OPERATIONS
+ */
+ protected abstract Iterable<QName> keys();
+
+ protected abstract void removeAttr(QName key);
+
+ @Override
+ public Set<Entry<QName, Object>> entrySet() {
+ Set<Entry<QName, Object>> result = new AttrSet();
+ return result;
+ }
+
+ @Override
+ public Class<?> getType(QName key) {
+ return String.class;
+ }
+
+ @Override
+ public boolean isMultiple(QName key) {
+ return false;
+ }
+
+ @Override
+ public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
+ Object value = get(key);
+ if (value == null)
+ return null;
+ if (value instanceof List) {
+ try {
+ List<A> res = (List<A>) value;
+ return Optional.of(res);
+ } catch (ClassCastException e) {
+ List<A> res = new ArrayList<>();
+ List<?> lst = (List<?>) value;
+ try {
+ for (Object o : lst) {
+ A item = (A) o;
+ res.add(item);
+ }
+ return Optional.of(res);
+ } catch (ClassCastException e1) {
+ return Optional.empty();
+ }
+ }
+ } else {// singleton
+ try {
+ A res = (A) value;
+ return Optional.of(Collections.singletonList(res));
+ } catch (ClassCastException e) {
+ return Optional.empty();
+ }
+ }
+ }
+
+ /*
+ * CONTENT OPERATIONS
+ */
+
+ @Override
+ public String getPath() {
+ List<Content> ancestors = new ArrayList<>();
+ collectAncestors(ancestors, this);
+ StringBuilder path = new StringBuilder();
+ for (Content c : ancestors) {
+ QName name = c.getName();
+ // FIXME
+ if (!CrName.ROOT.get().equals(name))
+ path.append('/').append(name);
+ }
+ return path.toString();
+ }
+
+ private void collectAncestors(List<Content> ancestors, Content content) {
+ if (content == null)
+ return;
+ ancestors.add(0, content);
+ collectAncestors(ancestors, content.getParent());
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+ // check whether clss is Object.class
+ return clss.isAssignableFrom(Object.class);
+ }
+
+ @Override
+ public String toString() {
+ return "content " + getPath();
+ }
+
+ /*
+ * SUB CLASSES
+ */
+
+ class AttrSet extends AbstractSet<Entry<QName, Object>> {
+
+ @Override
+ public Iterator<Entry<QName, Object>> iterator() {
+ final Iterator<QName> keys = keys().iterator();
+ Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
+
+ QName key = null;
+
+ @Override
+ public boolean hasNext() {
+ return keys.hasNext();
+ }
+
+ @Override
+ public Entry<QName, Object> next() {
+ key = keys.next();
+ // TODO check type
+ Optional<?> value = get(key, Object.class);
+ assert !value.isEmpty();
+ AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
+ return entry;
+ }
+
+ @Override
+ public void remove() {
+ if (key != null) {
+ AbstractContent.this.removeAttr(key);
+ } else {
+ throw new IllegalStateException("Iteration has not started");
+ }
+ }
+
+ };
+ return it;
+ }
+
+ @Override
+ public int size() {
+ int count = 0;
+ for (QName key : keys()) {
+ count++;
+ }
+ return count;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.api.acr.spi;
+
+import org.argeo.api.acr.Content;
+
+public interface ContentProvider {
+
+ Content get(ProvidedSession session, String mountPath, String relativePath);
+
+}
--- /dev/null
+package org.argeo.api.acr.spi;
+
+import org.argeo.api.acr.Content;
+
+public interface ProvidedContent extends Content {
+ ProvidedSession getSession();
+
+ ContentProvider getProvider();
+}
--- /dev/null
+package org.argeo.api.acr.spi;
+
+import org.argeo.api.acr.ContentRepository;
+
+public interface ProvidedRepository extends ContentRepository {
+}
--- /dev/null
+package org.argeo.api.acr.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.acr.ContentNameSupplier;
+import org.argeo.api.acr.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();
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.api.cms</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+Import-Package: \
+javax.security.*
+
+Export-Package: org.argeo.api.cms.*
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>argeo-commons</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.api.cms</artifactId>
+ <name>CMS API</name>
+ <packaging>jar</packaging>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.api.cms;
+
+import java.security.Principal;
+
+/** Marker for anonymous users. */
+public final class AnonymousPrincipal implements Principal {
+ private final String name = CmsConstants.ROLE_ANONYMOUS;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public String toString() {
+ return name.toString();
+ }
+}
--- /dev/null
+package org.argeo.api.cms;
+
+/** A 2D size. */
+public class Cms2DSize {
+ private Integer width;
+ private Integer height;
+
+ public Cms2DSize() {
+
+ }
+
+ public Cms2DSize(Integer width, Integer height) {
+ super();
+ this.width = width;
+ this.height = height;
+ }
+
+ public Integer getWidth() {
+ return width;
+ }
+
+ public void setWidth(Integer width) {
+ this.width = width;
+ }
+
+ public Integer getHeight() {
+ return height;
+ }
+
+ public void setHeight(Integer height) {
+ this.height = height;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.Set;
+
+/** An extensible user interface base on the CMS backend. */
+public interface CmsApp {
+ /**
+ * If {@link CmsUi#setData(String, Object)} is set with this property, it
+ * indicates a different UI (typically with another theming. The {@link CmsApp}
+ * can use this information, but it doesn't have to be set, in which case a
+ * default UI must be provided. The provided value must belong to the values
+ * returned by {@link CmsApp#getUiNames()}.
+ */
+ final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
+
+ Set<String> getUiNames();
+
+ CmsUi initUi(Object uiParent);
+
+ void refreshUi(CmsUi cmsUi, String state);
+
+ void setState(CmsUi cmsUi, String state);
+
+ CmsTheme getTheme(String uiName);
+
+ boolean allThemesAvailable();
+
+ void addCmsAppListener(CmsAppListener listener);
+
+ void removeCmsAppListener(CmsAppListener listener);
+}
--- /dev/null
+package org.argeo.api.cms;
+
+/** Notifies important events in a CMS App life cycle. */
+public interface CmsAppListener {
+ /** Theming has been updated and should be reloaded. */
+ void themingUpdated();
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/** The type of login context to use. */
+public enum CmsAuth {
+ NODE, USER, ANONYMOUS, DATA_ADMIN, SINGLE_USER, KEYRING;
+
+ public String getLoginContextName() {
+ return name();
+ }
+
+ @Override
+ public String toString() {
+ return getLoginContextName();
+ }
+
+ public LoginContext newLoginContext(CallbackHandler callbackHandler) throws LoginException {
+ return new LoginContext(getLoginContextName(), callbackHandler);
+ }
+
+ /*
+ * LOGIN CONTEXTS
+ */
+ /** @deprecated Use enum instead. */
+ @Deprecated
+ public static final String LOGIN_CONTEXT_NODE = NODE.getLoginContextName();
+ /** @deprecated Use enum instead. */
+ @Deprecated
+ public static final String LOGIN_CONTEXT_USER = USER.getLoginContextName();
+ /** @deprecated Use enum instead. */
+ @Deprecated
+ public static final String LOGIN_CONTEXT_ANONYMOUS = ANONYMOUS.getLoginContextName();
+ /** @deprecated Use enum instead. */
+ @Deprecated
+ public static final String LOGIN_CONTEXT_DATA_ADMIN = DATA_ADMIN.getLoginContextName();
+ /** @deprecated Use enum instead. */
+ @Deprecated
+ public static final String LOGIN_CONTEXT_SINGLE_USER = SINGLE_USER.getLoginContextName();
+ /** @deprecated Use enum instead. */
+ @Deprecated
+ public static final String LOGIN_CONTEXT_KEYRING = KEYRING.getLoginContextName();
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+public interface CmsConstants {
+ /*
+ * DN ATTRIBUTES (RFC 4514)
+ */
+ String CN = "cn";
+ String L = "l";
+ String ST = "st";
+ String O = "o";
+ String OU = "ou";
+ String C = "c";
+ String STREET = "street";
+ String DC = "dc";
+ String UID = "uid";
+
+ /*
+ * STANDARD ATTRIBUTES
+ */
+ String LABELED_URI = "labeledUri";
+
+ /*
+ * COMMON NAMES
+ */
+ String NODE = "node";
+
+ /*
+ * JCR CONVENTIONS
+ */
+ String NODE_REPOSITORY = NODE;
+ String EGO_REPOSITORY = "ego";
+ String SYS_WORKSPACE = "sys";
+ String HOME_WORKSPACE = "home";
+ String SRV_WORKSPACE = "srv";
+ String GUESTS_WORKSPACE = "guests";
+ String PUBLIC_WORKSPACE = "public";
+ String SECURITY_WORKSPACE = "security";
+
+ /*
+ * BASE DNs
+ */
+ String DEPLOY_BASEDN = "ou=deploy,ou=node";
+
+ /*
+ * STANDARD VALUES
+ */
+ String DEFAULT = "default";
+
+ /*
+ * RESERVED ROLES
+ */
+ String ROLES_BASEDN = "ou=roles,ou=node";
+ String TOKENS_BASEDN = "ou=tokens,ou=node";
+ String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN;
+ String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN;
+ String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN;
+ // Special system groups that cannot be edited:
+ // user U anonymous = everyone
+ String ROLE_USER = "cn=user," + ROLES_BASEDN;
+ String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
+ // Account lifecycle
+ String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
+
+ /*
+ * PATHS
+ */
+ String PATH_DATA = "/data";
+ String PATH_JCR = "/jcr";
+ String PATH_FILES = "/files";
+ // String PATH_JCR_PUB = "/pub";
+
+ /*
+ * FILE SYSTEMS
+ */
+ String SCHEME_NODE = NODE;
+
+ /*
+ * KERBEROS
+ */
+ String NODE_SERVICE = NODE;
+
+ /*
+ * INIT FRAMEWORK PROPERTIES
+ */
+ String NODE_INIT = "argeo.node.init";
+ String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale";
+ String I18N_LOCALES = "argeo.i18n.locales";
+ // Node Security
+ String ROLES_URI = "argeo.node.roles.uri";
+ String TOKENS_URI = "argeo.node.tokens.uri";
+ /** URI to an LDIF file or LDAP server used as initialization or backend */
+ String USERADMIN_URIS = "argeo.node.useradmin.uris";
+ // Transaction manager
+ String TRANSACTION_MANAGER = "argeo.node.transaction.manager";
+ String TRANSACTION_MANAGER_SIMPLE = "simple";
+ String TRANSACTION_MANAGER_BITRONIX = "bitronix";
+ // Node
+ /** Properties configuring the node repository */
+ String NODE_REPO_PROP_PREFIX = "argeo.node.repo.";
+ /** Additional standalone repositories, related to data models. */
+ String NODE_REPOS_PROP_PREFIX = "argeo.node.repos.";
+ // HTTP
+ String HTTP_PORT = "org.osgi.service.http.port";
+ String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
+ /**
+ * The HTTP header used to convey the DN of a client verified by a reverse
+ * proxy. Typically SSL_CLIENT_S_DN for Apache.
+ */
+ String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn";
+
+ /*
+ * PIDs
+ */
+ String NODE_STATE_PID = "org.argeo.api.state";
+ String NODE_DEPLOYMENT_PID = "org.argeo.api.deployment";
+ String NODE_INSTANCE_PID = "org.argeo.api.instance";
+
+ String NODE_KEYRING_PID = "org.argeo.api.keyring";
+ String NODE_FS_PROVIDER_PID = "org.argeo.api.fsProvider";
+
+ /*
+ * FACTORY PIDs
+ */
+ String NODE_REPOS_FACTORY_PID = "org.argeo.api.repos";
+ String NODE_USER_ADMIN_PID = "org.argeo.api.userAdmin";
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A logical view on this CMS instance, independently of a particular launch or
+ * deployment.
+ */
+public interface CmsContext {
+ /**
+ * To be used as an identifier of a workgroup, typically as a value for the
+ * 'businessCategory' attribute in LDAP.
+ */
+ public final static String WORKGROUP = "workgroup";
+
+ Locale getDefaultLocale();
+
+ List<Locale> getLocales();
+
+ Long getAvailableSince();
+
+
+ /** Mark this group as a workgroup */
+ void createWorkgroup(String groupDn);
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.Dictionary;
+
+/** A configured node deployment. */
+public interface CmsDeployment {
+
+ void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
+
+ Dictionary<String, Object> getProps(String factoryPid, String cn);
+}
--- /dev/null
+package org.argeo.api.cms;
+
+/** Abstraction of a simple edition life cycle. */
+public interface CmsEditable {
+
+ /** Whether the calling thread can edit, the value is immutable */
+ public Boolean canEdit();
+
+ public Boolean isEditing();
+
+ public void startEditing();
+
+ public void stopEditing();
+
+ public static CmsEditable NON_EDITABLE = new CmsEditable() {
+
+ @Override
+ public void stopEditing() {
+ }
+
+ @Override
+ public void startEditing() {
+ }
+
+ @Override
+ public Boolean isEditing() {
+ return false;
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return false;
+ }
+ };
+
+ public static CmsEditable ALWAYS_EDITING = new CmsEditable() {
+
+ @Override
+ public void stopEditing() {
+ }
+
+ @Override
+ public void startEditing() {
+ }
+
+ @Override
+ public Boolean isEditing() {
+ return true;
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return true;
+ }
+ };
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+/**
+ * Can be applied to {@link Enum}s in order to define events used by
+ * {@link CmsView#sendEvent(String, java.util.Map)}.
+ */
+public interface CmsEvent {
+ String name();
+
+ default String topic() {
+ return getTopicBase() + "/" + name();
+ }
+
+ default String getTopicBase() {
+ return "argeo/cms";
+ }
+
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.io.InputStream;
+
+/** Read and write access to images. */
+public interface CmsImageManager<V, M> {
+ /** Load image in control */
+ public Boolean load(M node, V control, Cms2DSize size);
+
+ /** @return (0,0) if not available */
+ public Cms2DSize getImageSize(M node);
+
+ /**
+ * The related <img> tag, with src, width and height set.
+ *
+ * @return null if not available
+ */
+ public String getImageTag(M node);
+
+ /**
+ * The related <img> tag, with url, width and height set. Caller must
+ * close the tag (or add additional attributes).
+ *
+ * @return null if not available
+ */
+ public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
+
+ /**
+ * Returns the remotely accessible URL of the image (registering it if
+ * needed) @return null if not available
+ */
+ public String getImageUrl(M node);
+
+// public Binary getImageBinary(Node node) throws RepositoryException;
+
+// public Image getSwtImage(Node node) throws RepositoryException;
+
+ /** @return URL */
+ public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
+
+ @Deprecated
+ default String uploadImage(M uploadFolder, String fileName, InputStream in) {
+ System.err.println("Context must be provided to " + CmsImageManager.class.getName());
+ return uploadImage(null, uploadFolder, fileName, in, null);
+ }
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Objects;
+
+/**
+ * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
+ * platform {@link Logger}.
+ */
+public interface CmsLog {
+ Logger getLogger();
+
+ default boolean isDebugEnabled() {
+ return getLogger().isLoggable(Level.DEBUG);
+ }
+
+ default boolean isErrorEnabled() {
+ return getLogger().isLoggable(Level.ERROR);
+ }
+
+ default boolean isInfoEnabled() {
+ return getLogger().isLoggable(Level.INFO);
+ }
+
+ default boolean isTraceEnabled() {
+ return getLogger().isLoggable(Level.TRACE);
+ }
+
+ default boolean isWarnEnabled() {
+ return getLogger().isLoggable(Level.WARNING);
+ }
+
+ /*
+ * TRACE
+ */
+
+ default void trace(String message) {
+ getLogger().log(Level.TRACE, message);
+ }
+
+ default void trace(Object message) {
+ getLogger().log(Level.TRACE, Objects.requireNonNull(message));
+ }
+
+ default void trace(String message, Throwable t) {
+ getLogger().log(Level.TRACE, message, t);
+ }
+
+ default void trace(Object message, Throwable t) {
+ trace(Objects.requireNonNull(message).toString(), t);
+ }
+
+ default void trace(String format, Object... arguments) {
+ getLogger().log(Level.TRACE, format, arguments);
+ }
+
+ /*
+ * DEBUG
+ */
+
+ default void debug(String message) {
+ getLogger().log(Level.DEBUG, message);
+ }
+
+ default void debug(Object message) {
+ getLogger().log(Level.DEBUG, message);
+ }
+
+ default void debug(String message, Throwable t) {
+ getLogger().log(Level.DEBUG, message, t);
+ }
+
+ default void debug(Object message, Throwable t) {
+ debug(Objects.requireNonNull(message).toString(), t);
+ }
+
+ default void debug(String format, Object... arguments) {
+ getLogger().log(Level.DEBUG, format, arguments);
+ }
+
+ /*
+ * INFO
+ */
+
+ default void info(String message) {
+ getLogger().log(Level.INFO, message);
+ }
+
+ default void info(Object message) {
+ getLogger().log(Level.INFO, message);
+ }
+
+ default void info(String message, Throwable t) {
+ getLogger().log(Level.INFO, message, t);
+ }
+
+ default void info(Object message, Throwable t) {
+ info(Objects.requireNonNull(message).toString(), t);
+ }
+
+ default void info(String format, Object... arguments) {
+ getLogger().log(Level.INFO, format, arguments);
+ }
+
+ /*
+ * WARN
+ */
+
+ default void warn(String message) {
+ getLogger().log(Level.WARNING, message);
+ }
+
+ default void warn(Object message) {
+ getLogger().log(Level.WARNING, message);
+ }
+
+ default void warn(String message, Throwable t) {
+ getLogger().log(Level.WARNING, message, t);
+ }
+
+ default void warn(Object message, Throwable t) {
+ warn(Objects.requireNonNull(message).toString(), t);
+ }
+
+ default void warn(String format, Object... arguments) {
+ getLogger().log(Level.WARNING, format, arguments);
+ }
+
+ /*
+ * ERROR
+ */
+
+ default void error(String message) {
+ getLogger().log(Level.ERROR, message);
+ }
+
+ default void error(Object message) {
+ getLogger().log(Level.ERROR, message);
+ }
+
+ default void error(String message, Throwable t) {
+ getLogger().log(Level.ERROR, message, t);
+ }
+
+ default void error(Object message, Throwable t) {
+ error(Objects.requireNonNull(message).toString(), t);
+ }
+
+ default void error(String format, Object... arguments) {
+ getLogger().log(Level.ERROR, format, arguments);
+ }
+
+ /*
+ * STATIC UTILITIES
+ */
+
+ static CmsLog getLog(Class<?> clss) {
+ return getLog(Objects.requireNonNull(clss).getName());
+ }
+
+ static CmsLog getLog(String name) {
+ Logger logger = System.getLogger(Objects.requireNonNull(name));
+ return new LoggerWrapper(logger);
+ }
+
+ /** A trivial implementation wrapping a platform logger. */
+ static class LoggerWrapper implements CmsLog {
+ private final Logger logger;
+
+ LoggerWrapper(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public Logger getLogger() {
+ return logger;
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.time.ZonedDateTime;
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+
+/** An authenticated user session. */
+public interface CmsSession {
+ final static String USER_DN = "DN";
+ final static String SESSION_UUID = "entryUUID";
+ final static String SESSION_LOCAL_ID = "uniqueIdentifier";
+
+ UUID getUuid();
+
+ String getUserRole();
+
+ LdapName getUserDn();
+
+ String getLocalId();
+
+ String getDisplayName();
+// Authorization getAuthorization();
+
+ Subject getSubject();
+
+ boolean isAnonymous();
+
+ ZonedDateTime getCreationTime();
+
+ ZonedDateTime getEnd();
+
+ Locale getLocale();
+
+ boolean isValid();
+
+ void registerView(String uid, Object view);
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+
+/**
+ * The ID of a {@link CmsSession}, which must be available in the private
+ * credentials of an authenticated {@link Subject}.
+ */
+public class CmsSessionId {
+ private final UUID uuid;
+
+ public CmsSessionId(UUID value) {
+ if (value == null)
+ throw new IllegalArgumentException("Value cannot be null");
+ this.uuid = value;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public int hashCode() {
+ return uuid.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof CmsSessionId && ((CmsSessionId) obj).getUuid().equals(uuid);
+ }
+
+ @Override
+ public String toString() {
+ return "Node Session " + uuid;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+/** A running node process. */
+public interface CmsState {
+ String getHostname();
+
+ Long getAvailableSince();
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
+public interface CmsStyle {
+ String name();
+
+ /** @deprecated use {@link #style()} instead. */
+ @Deprecated
+ default String toStyleClass() {
+ return style();
+ }
+
+ default String style() {
+ String classPrefix = getClassPrefix();
+ return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
+ }
+
+ default String getClassPrefix() {
+ return "";
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+/** A CMS theme which can be applied to web apps as well as desktop apps. */
+public interface CmsTheme {
+ /** Unique ID of this theme. */
+ String getThemeId();
+
+ /**
+ * Load a resource as an input stream, base don its relative path, or
+ * <code>null</code> if not found
+ */
+ InputStream getResourceAsStream(String resourceName) throws IOException;
+
+ /** Relative paths to standard web CSS. */
+ Set<String> getWebCssPaths();
+
+ /** Relative paths to RAP specific CSS. */
+ Set<String> getRapCssPaths();
+
+ /** Relative paths to SWT specific CSS. */
+ Set<String> getSwtCssPaths();
+
+ /** Relative paths to images such as icons. */
+ Set<String> getImagesPaths();
+
+ /** Relative paths to fonts. */
+ Set<String> getFontsPaths();
+
+ /** Tags to be added to the header section of the HTML page. */
+ String getHtmlHeaders();
+
+ /** The HTML body to use. */
+ String getBodyHtml();
+
+ /** The default icon size (typically the smallest). */
+ Integer getDefaultIconSize();
+
+ /** Loads one of the relative path provided by the other methods. */
+ InputStream loadPath(String path) throws IOException;
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+public interface CmsUi {
+ Object getData(String key);
+ void setData(String key, Object value);
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.login.LoginContext;
+
+/** Provides interaction with the CMS system. */
+public interface CmsView {
+ final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
+ // String KEY = "org.argeo.cms.ui.view";
+
+ String getUid();
+
+ UxContext getUxContext();
+
+ // NAVIGATION
+ void navigateTo(String state);
+
+ // SECURITY
+ void authChange(LoginContext loginContext);
+
+ void logout();
+
+ // void registerCallbackHandler(CallbackHandler callbackHandler);
+
+ // SERVICES
+ void exception(Throwable e);
+
+ CmsImageManager<?, ?> getImageManager();
+
+ boolean isAnonymous();
+
+ /**
+ * Send an event to this topic. Does nothing by default., but if implemented it
+ * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
+ */
+ default void sendEvent(String topic, Map<String, Object> properties) {
+
+ }
+
+ /**
+ * Convenience methods for when {@link #sendEvent(String, Map)} only requires
+ * one single parameter.
+ */
+ default void sendEvent(String topic, String param, Object value) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(param, value);
+ sendEvent(topic, properties);
+ }
+
+ default void applyStyles(Object widget) {
+
+ }
+
+ default <T> T doAs(PrivilegedAction<T> action) {
+ throw new UnsupportedOperationException();
+ }
+
+ default Void runAs(Runnable runnable) {
+ return doAs(new PrivilegedAction<Void>() {
+
+ @Override
+ public Void run() {
+ if (runnable != null)
+ runnable.run();
+ return null;
+ }
+ });
+ }
+
+ default void stateChanged(String state, String title) {
+ }
+
+ default CmsSession getCmsSession() {
+ throw new UnsupportedOperationException();
+ }
+
+ default Object getData(String key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ default <T> T getUiContext(Class<T> clss) {
+ return (T) getData(clss.getName());
+ }
+
+ default <T> void setUiContext(Class<T> clss, T instance) {
+ setData(clss.getName(), instance);
+ }
+
+ default void setData(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.security.Principal;
+
+/** Allows to modify any data. */
+public final class DataAdminPrincipal implements Principal {
+ private final String name = CmsConstants.ROLE_DATA_ADMIN;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof DataAdminPrincipal;
+ }
+
+ @Override
+ public String toString() {
+ return name.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.function.BiFunction;
+
+/**
+ * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
+ * order to create a view part (W) which can then be further configured. Such
+ * object can be used as services and reference other part of the model which
+ * are relevant for all created UI part.
+ */
+@FunctionalInterface
+public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
+ W createUiPart(V parent, M context);
+
+ /**
+ * Whether this parent view is supported.
+ *
+ * @return true by default.
+ */
+ default boolean isViewSupported(V parent) {
+ return true;
+ }
+
+ /**
+ * Whether this context is supported.
+ *
+ * @return true by default.
+ */
+ default boolean isModelSupported(M context) {
+ return true;
+ }
+
+ default W apply(V parent, M context) {
+ if (!isViewSupported(parent))
+ throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
+ if (!isModelSupported(context))
+ throw new IllegalArgumentException("Model context " + context + "is not supported.");
+ return createUiPart(parent, context);
+ }
+
+ default W createUiPart(V parent) {
+ return createUiPart(parent, null);
+ }
+}
--- /dev/null
+package org.argeo.api.cms;
+
+public interface UxContext {
+ boolean isPortrait();
+
+ boolean isLandscape();
+
+ boolean isSquare();
+
+ boolean isSmall();
+
+ /**
+ * Is a production environment (must be false by default, and be explicitly
+ * set during the CMS deployment). When false, it can activate additional UI
+ * capabilities in order to facilitate QA.
+ */
+ boolean isMasterData();
+}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.api</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- <buildCommand>
- <name>org.eclipse.pde.ManifestBuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- <buildCommand>
- <name>org.eclipse.pde.SchemaBuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.pde.PluginNature</nature>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
+++ /dev/null
-/MANIFEST.MF
+++ /dev/null
-Import-Package: javax.naming.*,\
-javax.security.*
-
-Export-Package: org.argeo.api.*
\ No newline at end of file
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.argeo.commons</groupId>
- <artifactId>argeo-commons</artifactId>
- <version>2.3-SNAPSHOT</version>
- <relativePath>..</relativePath>
- </parent>
- <artifactId>org.argeo.api</artifactId>
- <name>CMS APIs</name>
- <packaging>jar</packaging>
- <dependencies>
- </dependencies>
-</project>
\ No newline at end of file
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.security.Principal;
-
-/** Marker for anonymous users. */
-public final class AnonymousPrincipal implements Principal {
- private final String name = CmsConstants.ROLE_ANONYMOUS;
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return this == obj;
- }
-
- @Override
- public String toString() {
- return name.toString();
- }
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** A 2D size. */
-public class Cms2DSize {
- private Integer width;
- private Integer height;
-
- public Cms2DSize() {
-
- }
-
- public Cms2DSize(Integer width, Integer height) {
- super();
- this.width = width;
- this.height = height;
- }
-
- public Integer getWidth() {
- return width;
- }
-
- public void setWidth(Integer width) {
- this.width = width;
- }
-
- public Integer getHeight() {
- return height;
- }
-
- public void setHeight(Integer height) {
- this.height = height;
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.util.Set;
-
-/** An extensible user interface base on the CMS backend. */
-public interface CmsApp {
- /**
- * If {@link CmsUi#setData(String, Object)} is set with this property, it
- * indicates a different UI (typically with another theming. The {@link CmsApp}
- * can use this information, but it doesn't have to be set, in which case a
- * default UI must be provided. The provided value must belong to the values
- * returned by {@link CmsApp#getUiNames()}.
- */
- final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
-
- Set<String> getUiNames();
-
- CmsUi initUi(Object uiParent);
-
- void refreshUi(CmsUi cmsUi, String state);
-
- void setState(CmsUi cmsUi, String state);
-
- CmsTheme getTheme(String uiName);
-
- boolean allThemesAvailable();
-
- void addCmsAppListener(CmsAppListener listener);
-
- void removeCmsAppListener(CmsAppListener listener);
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** Notifies important events in a CMS App life cycle. */
-public interface CmsAppListener {
- /** Theming has been updated and should be reloaded. */
- void themingUpdated();
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-/** The type of login context to use. */
-public enum CmsAuth {
- NODE, USER, ANONYMOUS, DATA_ADMIN, SINGLE_USER, KEYRING;
-
- public String getLoginContextName() {
- return name();
- }
-
- @Override
- public String toString() {
- return getLoginContextName();
- }
-
- public LoginContext newLoginContext(CallbackHandler callbackHandler) throws LoginException {
- return new LoginContext(getLoginContextName(), callbackHandler);
- }
-
- /*
- * LOGIN CONTEXTS
- */
- /** @deprecated Use enum instead. */
- @Deprecated
- public static final String LOGIN_CONTEXT_NODE = NODE.getLoginContextName();
- /** @deprecated Use enum instead. */
- @Deprecated
- public static final String LOGIN_CONTEXT_USER = USER.getLoginContextName();
- /** @deprecated Use enum instead. */
- @Deprecated
- public static final String LOGIN_CONTEXT_ANONYMOUS = ANONYMOUS.getLoginContextName();
- /** @deprecated Use enum instead. */
- @Deprecated
- public static final String LOGIN_CONTEXT_DATA_ADMIN = DATA_ADMIN.getLoginContextName();
- /** @deprecated Use enum instead. */
- @Deprecated
- public static final String LOGIN_CONTEXT_SINGLE_USER = SINGLE_USER.getLoginContextName();
- /** @deprecated Use enum instead. */
- @Deprecated
- public static final String LOGIN_CONTEXT_KEYRING = KEYRING.getLoginContextName();
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-public interface CmsConstants {
- /*
- * DN ATTRIBUTES (RFC 4514)
- */
- String CN = "cn";
- String L = "l";
- String ST = "st";
- String O = "o";
- String OU = "ou";
- String C = "c";
- String STREET = "street";
- String DC = "dc";
- String UID = "uid";
-
- /*
- * STANDARD ATTRIBUTES
- */
- String LABELED_URI = "labeledUri";
-
- /*
- * COMMON NAMES
- */
- String NODE = "node";
-
- /*
- * JCR CONVENTIONS
- */
- String NODE_REPOSITORY = NODE;
- String EGO_REPOSITORY = "ego";
- String SYS_WORKSPACE = "sys";
- String HOME_WORKSPACE = "home";
- String SRV_WORKSPACE = "srv";
- String GUESTS_WORKSPACE = "guests";
- String PUBLIC_WORKSPACE = "public";
- String SECURITY_WORKSPACE = "security";
-
- /*
- * BASE DNs
- */
- String DEPLOY_BASEDN = "ou=deploy,ou=node";
-
- /*
- * STANDARD VALUES
- */
- String DEFAULT = "default";
-
- /*
- * RESERVED ROLES
- */
- String ROLES_BASEDN = "ou=roles,ou=node";
- String TOKENS_BASEDN = "ou=tokens,ou=node";
- String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN;
- String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN;
- String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN;
- // Special system groups that cannot be edited:
- // user U anonymous = everyone
- String ROLE_USER = "cn=user," + ROLES_BASEDN;
- String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
- // Account lifecycle
- String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
-
- /*
- * PATHS
- */
- String PATH_DATA = "/data";
- String PATH_JCR = "/jcr";
- String PATH_FILES = "/files";
- // String PATH_JCR_PUB = "/pub";
-
- /*
- * FILE SYSTEMS
- */
- String SCHEME_NODE = NODE;
-
- /*
- * KERBEROS
- */
- String NODE_SERVICE = NODE;
-
- /*
- * INIT FRAMEWORK PROPERTIES
- */
- String NODE_INIT = "argeo.node.init";
- String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale";
- String I18N_LOCALES = "argeo.i18n.locales";
- // Node Security
- String ROLES_URI = "argeo.node.roles.uri";
- String TOKENS_URI = "argeo.node.tokens.uri";
- /** URI to an LDIF file or LDAP server used as initialization or backend */
- String USERADMIN_URIS = "argeo.node.useradmin.uris";
- // Transaction manager
- String TRANSACTION_MANAGER = "argeo.node.transaction.manager";
- String TRANSACTION_MANAGER_SIMPLE = "simple";
- String TRANSACTION_MANAGER_BITRONIX = "bitronix";
- // Node
- /** Properties configuring the node repository */
- String NODE_REPO_PROP_PREFIX = "argeo.node.repo.";
- /** Additional standalone repositories, related to data models. */
- String NODE_REPOS_PROP_PREFIX = "argeo.node.repos.";
- // HTTP
- String HTTP_PORT = "org.osgi.service.http.port";
- String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
- /**
- * The HTTP header used to convey the DN of a client verified by a reverse
- * proxy. Typically SSL_CLIENT_S_DN for Apache.
- */
- String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn";
-
- /*
- * PIDs
- */
- String NODE_STATE_PID = "org.argeo.api.state";
- String NODE_DEPLOYMENT_PID = "org.argeo.api.deployment";
- String NODE_INSTANCE_PID = "org.argeo.api.instance";
-
- String NODE_KEYRING_PID = "org.argeo.api.keyring";
- String NODE_FS_PROVIDER_PID = "org.argeo.api.fsProvider";
-
- /*
- * FACTORY PIDs
- */
- String NODE_REPOS_FACTORY_PID = "org.argeo.api.repos";
- String NODE_USER_ADMIN_PID = "org.argeo.api.userAdmin";
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.util.List;
-import java.util.Locale;
-
-/**
- * A logical view on this CMS instance, independently of a particular launch or
- * deployment.
- */
-public interface CmsContext {
- /**
- * To be used as an identifier of a workgroup, typically as a value for the
- * 'businessCategory' attribute in LDAP.
- */
- public final static String WORKGROUP = "workgroup";
-
- Locale getDefaultLocale();
-
- List<Locale> getLocales();
-
- Long getAvailableSince();
-
-
- /** Mark this group as a workgroup */
- void createWorkgroup(String groupDn);
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.util.Dictionary;
-
-/** A configured node deployment. */
-public interface CmsDeployment {
-
- void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
-
- Dictionary<String, Object> getProps(String factoryPid, String cn);
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** Abstraction of a simple edition life cycle. */
-public interface CmsEditable {
-
- /** Whether the calling thread can edit, the value is immutable */
- public Boolean canEdit();
-
- public Boolean isEditing();
-
- public void startEditing();
-
- public void stopEditing();
-
- public static CmsEditable NON_EDITABLE = new CmsEditable() {
-
- @Override
- public void stopEditing() {
- }
-
- @Override
- public void startEditing() {
- }
-
- @Override
- public Boolean isEditing() {
- return false;
- }
-
- @Override
- public Boolean canEdit() {
- return false;
- }
- };
-
- public static CmsEditable ALWAYS_EDITING = new CmsEditable() {
-
- @Override
- public void stopEditing() {
- }
-
- @Override
- public void startEditing() {
- }
-
- @Override
- public Boolean isEditing() {
- return true;
- }
-
- @Override
- public Boolean canEdit() {
- return true;
- }
- };
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-/**
- * Can be applied to {@link Enum}s in order to define events used by
- * {@link CmsView#sendEvent(String, java.util.Map)}.
- */
-public interface CmsEvent {
- String name();
-
- default String topic() {
- return getTopicBase() + "/" + name();
- }
-
- default String getTopicBase() {
- return "argeo/cms";
- }
-
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.io.InputStream;
-
-/** Read and write access to images. */
-public interface CmsImageManager<V, M> {
- /** Load image in control */
- public Boolean load(M node, V control, Cms2DSize size);
-
- /** @return (0,0) if not available */
- public Cms2DSize getImageSize(M node);
-
- /**
- * The related <img> tag, with src, width and height set.
- *
- * @return null if not available
- */
- public String getImageTag(M node);
-
- /**
- * The related <img> tag, with url, width and height set. Caller must
- * close the tag (or add additional attributes).
- *
- * @return null if not available
- */
- public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
-
- /**
- * Returns the remotely accessible URL of the image (registering it if
- * needed) @return null if not available
- */
- public String getImageUrl(M node);
-
-// public Binary getImageBinary(Node node) throws RepositoryException;
-
-// public Image getSwtImage(Node node) throws RepositoryException;
-
- /** @return URL */
- public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
-
- @Deprecated
- default String uploadImage(M uploadFolder, String fileName, InputStream in) {
- System.err.println("Context must be provided to " + CmsImageManager.class.getName());
- return uploadImage(null, uploadFolder, fileName, in, null);
- }
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.util.Objects;
-
-/**
- * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
- * platform {@link Logger}.
- */
-public interface CmsLog {
- Logger getLogger();
-
- default boolean isDebugEnabled() {
- return getLogger().isLoggable(Level.DEBUG);
- }
-
- default boolean isErrorEnabled() {
- return getLogger().isLoggable(Level.ERROR);
- }
-
- default boolean isInfoEnabled() {
- return getLogger().isLoggable(Level.INFO);
- }
-
- default boolean isTraceEnabled() {
- return getLogger().isLoggable(Level.TRACE);
- }
-
- default boolean isWarnEnabled() {
- return getLogger().isLoggable(Level.WARNING);
- }
-
- /*
- * TRACE
- */
-
- default void trace(String message) {
- getLogger().log(Level.TRACE, message);
- }
-
- default void trace(Object message) {
- getLogger().log(Level.TRACE, Objects.requireNonNull(message));
- }
-
- default void trace(String message, Throwable t) {
- getLogger().log(Level.TRACE, message, t);
- }
-
- default void trace(Object message, Throwable t) {
- trace(Objects.requireNonNull(message).toString(), t);
- }
-
- default void trace(String format, Object... arguments) {
- getLogger().log(Level.TRACE, format, arguments);
- }
-
- /*
- * DEBUG
- */
-
- default void debug(String message) {
- getLogger().log(Level.DEBUG, message);
- }
-
- default void debug(Object message) {
- getLogger().log(Level.DEBUG, message);
- }
-
- default void debug(String message, Throwable t) {
- getLogger().log(Level.DEBUG, message, t);
- }
-
- default void debug(Object message, Throwable t) {
- debug(Objects.requireNonNull(message).toString(), t);
- }
-
- default void debug(String format, Object... arguments) {
- getLogger().log(Level.DEBUG, format, arguments);
- }
-
- /*
- * INFO
- */
-
- default void info(String message) {
- getLogger().log(Level.INFO, message);
- }
-
- default void info(Object message) {
- getLogger().log(Level.INFO, message);
- }
-
- default void info(String message, Throwable t) {
- getLogger().log(Level.INFO, message, t);
- }
-
- default void info(Object message, Throwable t) {
- info(Objects.requireNonNull(message).toString(), t);
- }
-
- default void info(String format, Object... arguments) {
- getLogger().log(Level.INFO, format, arguments);
- }
-
- /*
- * WARN
- */
-
- default void warn(String message) {
- getLogger().log(Level.WARNING, message);
- }
-
- default void warn(Object message) {
- getLogger().log(Level.WARNING, message);
- }
-
- default void warn(String message, Throwable t) {
- getLogger().log(Level.WARNING, message, t);
- }
-
- default void warn(Object message, Throwable t) {
- warn(Objects.requireNonNull(message).toString(), t);
- }
-
- default void warn(String format, Object... arguments) {
- getLogger().log(Level.WARNING, format, arguments);
- }
-
- /*
- * ERROR
- */
-
- default void error(String message) {
- getLogger().log(Level.ERROR, message);
- }
-
- default void error(Object message) {
- getLogger().log(Level.ERROR, message);
- }
-
- default void error(String message, Throwable t) {
- getLogger().log(Level.ERROR, message, t);
- }
-
- default void error(Object message, Throwable t) {
- error(Objects.requireNonNull(message).toString(), t);
- }
-
- default void error(String format, Object... arguments) {
- getLogger().log(Level.ERROR, format, arguments);
- }
-
- /*
- * STATIC UTILITIES
- */
-
- static CmsLog getLog(Class<?> clss) {
- return getLog(Objects.requireNonNull(clss).getName());
- }
-
- static CmsLog getLog(String name) {
- Logger logger = System.getLogger(Objects.requireNonNull(name));
- return new LoggerWrapper(logger);
- }
-
- /** A trivial implementation wrapping a platform logger. */
- static class LoggerWrapper implements CmsLog {
- private final Logger logger;
-
- LoggerWrapper(Logger logger) {
- this.logger = logger;
- }
-
- @Override
- public Logger getLogger() {
- return logger;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.time.ZonedDateTime;
-import java.util.Locale;
-import java.util.UUID;
-
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-/** An authenticated user session. */
-public interface CmsSession {
- final static String USER_DN = "DN";
- final static String SESSION_UUID = "entryUUID";
- final static String SESSION_LOCAL_ID = "uniqueIdentifier";
-
- UUID getUuid();
-
- String getUserRole();
-
- LdapName getUserDn();
-
- String getLocalId();
-
- String getDisplayName();
-// Authorization getAuthorization();
-
- Subject getSubject();
-
- boolean isAnonymous();
-
- ZonedDateTime getCreationTime();
-
- ZonedDateTime getEnd();
-
- Locale getLocale();
-
- boolean isValid();
-
- void registerView(String uid, Object view);
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-
-/**
- * The ID of a {@link CmsSession}, which must be available in the private
- * credentials of an authenticated {@link Subject}.
- */
-public class CmsSessionId {
- private final UUID uuid;
-
- public CmsSessionId(UUID value) {
- if (value == null)
- throw new IllegalArgumentException("Value cannot be null");
- this.uuid = value;
- }
-
- public UUID getUuid() {
- return uuid;
- }
-
- @Override
- public int hashCode() {
- return uuid.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof CmsSessionId && ((CmsSessionId) obj).getUuid().equals(uuid);
- }
-
- @Override
- public String toString() {
- return "Node Session " + uuid;
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** A running node process. */
-public interface CmsState {
- String getHostname();
-
- Long getAvailableSince();
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
-public interface CmsStyle {
- String name();
-
- /** @deprecated use {@link #style()} instead. */
- @Deprecated
- default String toStyleClass() {
- return style();
- }
-
- default String style() {
- String classPrefix = getClassPrefix();
- return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
- }
-
- default String getClassPrefix() {
- return "";
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Set;
-
-/** A CMS theme which can be applied to web apps as well as desktop apps. */
-public interface CmsTheme {
- /** Unique ID of this theme. */
- String getThemeId();
-
- /**
- * Load a resource as an input stream, base don its relative path, or
- * <code>null</code> if not found
- */
- InputStream getResourceAsStream(String resourceName) throws IOException;
-
- /** Relative paths to standard web CSS. */
- Set<String> getWebCssPaths();
-
- /** Relative paths to RAP specific CSS. */
- Set<String> getRapCssPaths();
-
- /** Relative paths to SWT specific CSS. */
- Set<String> getSwtCssPaths();
-
- /** Relative paths to images such as icons. */
- Set<String> getImagesPaths();
-
- /** Relative paths to fonts. */
- Set<String> getFontsPaths();
-
- /** Tags to be added to the header section of the HTML page. */
- String getHtmlHeaders();
-
- /** The HTML body to use. */
- String getBodyHtml();
-
- /** The default icon size (typically the smallest). */
- Integer getDefaultIconSize();
-
- /** Loads one of the relative path provided by the other methods. */
- InputStream loadPath(String path) throws IOException;
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-public interface CmsUi {
- Object getData(String key);
- void setData(String key, Object value);
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.security.auth.login.LoginContext;
-
-/** Provides interaction with the CMS system. */
-public interface CmsView {
- final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
- // String KEY = "org.argeo.cms.ui.view";
-
- String getUid();
-
- UxContext getUxContext();
-
- // NAVIGATION
- void navigateTo(String state);
-
- // SECURITY
- void authChange(LoginContext loginContext);
-
- void logout();
-
- // void registerCallbackHandler(CallbackHandler callbackHandler);
-
- // SERVICES
- void exception(Throwable e);
-
- CmsImageManager<?, ?> getImageManager();
-
- boolean isAnonymous();
-
- /**
- * Send an event to this topic. Does nothing by default., but if implemented it
- * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
- */
- default void sendEvent(String topic, Map<String, Object> properties) {
-
- }
-
- /**
- * Convenience methods for when {@link #sendEvent(String, Map)} only requires
- * one single parameter.
- */
- default void sendEvent(String topic, String param, Object value) {
- Map<String, Object> properties = new HashMap<>();
- properties.put(param, value);
- sendEvent(topic, properties);
- }
-
- default void applyStyles(Object widget) {
-
- }
-
- default <T> T doAs(PrivilegedAction<T> action) {
- throw new UnsupportedOperationException();
- }
-
- default Void runAs(Runnable runnable) {
- return doAs(new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- if (runnable != null)
- runnable.run();
- return null;
- }
- });
- }
-
- default void stateChanged(String state, String title) {
- }
-
- default CmsSession getCmsSession() {
- throw new UnsupportedOperationException();
- }
-
- default Object getData(String key) {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings("unchecked")
- default <T> T getUiContext(Class<T> clss) {
- return (T) getData(clss.getName());
- }
-
- default <T> void setUiContext(Class<T> clss, T instance) {
- setData(clss.getName(), instance);
- }
-
- default void setData(String key, Object value) {
- throw new UnsupportedOperationException();
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.security.Principal;
-
-/** Allows to modify any data. */
-public final class DataAdminPrincipal implements Principal {
- private final String name = CmsConstants.ROLE_DATA_ADMIN;
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof DataAdminPrincipal;
- }
-
- @Override
- public String toString() {
- return name.toString();
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.util.function.BiFunction;
-
-/**
- * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
- * order to create a view part (W) which can then be further configured. Such
- * object can be used as services and reference other part of the model which
- * are relevant for all created UI part.
- */
-@FunctionalInterface
-public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
- W createUiPart(V parent, M context);
-
- /**
- * Whether this parent view is supported.
- *
- * @return true by default.
- */
- default boolean isViewSupported(V parent) {
- return true;
- }
-
- /**
- * Whether this context is supported.
- *
- * @return true by default.
- */
- default boolean isModelSupported(M context) {
- return true;
- }
-
- default W apply(V parent, M context) {
- if (!isViewSupported(parent))
- throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
- if (!isModelSupported(context))
- throw new IllegalArgumentException("Model context " + context + "is not supported.");
- return createUiPart(parent, context);
- }
-
- default W createUiPart(V parent) {
- return createUiPart(parent, null);
- }
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-public interface UxContext {
- boolean isPortrait();
-
- boolean isLandscape();
-
- boolean isSquare();
-
- boolean isSmall();
-
- /**
- * Is a production environment (must be false by default, and be explicitly
- * set during the CMS deployment). When false, it can activate additional UI
- * capabilities in order to facilitate QA.
- */
- boolean isMasterData();
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-/**
- * An attribute type MUST consistently parse a string to an object so that
- * <code>parse(obj.toString()).equals(obj)</code> is verified.
- * {@link #format(Object)} can be overridden to provide more efficient
- * implementations but the returned
- * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
- * is verified.
- */
-public interface AttributeFormatter<T> {
- /** Parses a String to a Java object. */
- T parse(String str) throws IllegalArgumentException;
-
- /** Default implementation returns {@link Object#toString()} on the argument. */
- default String format(T obj) {
- return obj.toString();
- }
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringTokenizer;
-
-/** A name that can be expressed with various conventions. */
-public class CompositeString {
- public final static Character UNDERSCORE = Character.valueOf('_');
- public final static Character SPACE = Character.valueOf(' ');
- public final static Character DASH = Character.valueOf('-');
-
- private final String[] parts;
-
- // optimisation
- private final int hashCode;
-
- public CompositeString(String str) {
- Objects.requireNonNull(str, "String cannot be null");
- if ("".equals(str.trim()))
- throw new IllegalArgumentException("String cannot be empty");
- if (!str.equals(str.trim()))
- throw new IllegalArgumentException("String must be trimmed");
- this.parts = toParts(str);
- hashCode = hashCode(this.parts);
- }
-
- public String toString(char separator, boolean upperCase) {
- StringBuilder sb = null;
- for (String part : parts) {
- if (sb == null) {
- sb = new StringBuilder();
- } else {
- sb.append(separator);
- }
- sb.append(upperCase ? part.toUpperCase() : part);
- }
- return sb.toString();
- }
-
- public String toStringCaml(boolean firstCharUpperCase) {
- StringBuilder sb = null;
- for (String part : parts) {
- if (sb == null) {// first
- sb = new StringBuilder();
- sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
- } else {
- sb.append(Character.toUpperCase(part.charAt(0)));
- }
-
- if (part.length() > 1)
- sb.append(part.substring(1));
- }
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null || !(obj instanceof CompositeString))
- return false;
-
- CompositeString other = (CompositeString) obj;
- return Arrays.equals(parts, other.parts);
- }
-
- @Override
- public String toString() {
- return toString(DASH, false);
- }
-
- public static String[] toParts(String str) {
- Character separator = null;
- if (str.indexOf(UNDERSCORE) >= 0) {
- checkNo(str, SPACE);
- checkNo(str, DASH);
- separator = UNDERSCORE;
- } else if (str.indexOf(DASH) >= 0) {
- checkNo(str, SPACE);
- checkNo(str, UNDERSCORE);
- separator = DASH;
- } else if (str.indexOf(SPACE) >= 0) {
- checkNo(str, DASH);
- checkNo(str, UNDERSCORE);
- separator = SPACE;
- }
-
- List<String> res = new ArrayList<>();
- if (separator != null) {
- StringTokenizer st = new StringTokenizer(str, separator.toString());
- while (st.hasMoreTokens()) {
- res.add(st.nextToken().toLowerCase());
- }
- } 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)) {
- if (current != null)
- res.add(current.toString());
- current = new StringBuilder();
- }
- if (current == null)// first char is lower case
- current = new StringBuilder();
- current.append(Character.toLowerCase(c));
- }
- res.add(current.toString());
- }
- return res.toArray(new String[res.size()]);
- }
-
- private static void checkNo(String str, Character c) {
- if (str.indexOf(c) >= 0) {
- throw new IllegalArgumentException("Only one kind of sperator is allowed");
- }
- }
-
- private static int hashCode(String[] parts) {
- int hashCode = 0;
- for (String part : parts) {
- hashCode = hashCode + part.hashCode();
- }
- return hashCode;
- }
-
- 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 new CompositeString("MY_COMPLEX_NAME").equals(camlName);
- assert new CompositeString("My complex Name").equals(camlName);
- assert new CompositeString("MyComplexName").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));
-
- return CompositeString.class.desiredAssertionStatus();
- }
-
- public static void main(String[] args) {
- System.out.println(smokeTests());
- }
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-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<QName, Object> {
-
- QName getName();
-
- String getPath();
-
- Content getParent();
-
- /*
- * ATTRIBUTES OPERATIONS
- */
-
- <A> Optional<A> get(QName key, Class<A> clss);
-
- 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));
- }
-
- Class<?> getType(QName key);
-
- boolean isMultiple(QName key);
-
- <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
-
- default <A> List<A> getMultiple(QName key) {
- Class<A> type;
- try {
- type = (Class<A>) getType(key);
- } catch (ClassCastException e) {
- throw new IllegalArgumentException("Requested type is not the default type");
- }
- Optional<List<A>> res = getMultiple(key, type);
- if (res == null)
- return null;
- else {
- if (res.isEmpty())
- throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
- return res.get();
- }
- }
-
- /*
- * CONTENT OPERATIONS
- */
- 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();
-
- /*
- * DEFAULT METHODS
- */
- default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
- throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
- }
-
- default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
- throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
- }
-
- /*
- * 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);
-// }
-
- /*
- * EXPERIMENTAL UNSUPPORTED
- */
- default boolean hasText() {
- return false;
- }
-
- default String getText() {
- throw new UnsupportedOperationException();
- }
-
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-/** When a feature is not supported by the underlying repository. */
-public class ContentFeatureUnsupportedException extends UnsupportedOperationException {
- private static final long serialVersionUID = 3193936026343114949L;
-
- public ContentFeatureUnsupportedException() {
- }
-
- public ContentFeatureUnsupportedException(String message) {
- super(message);
- }
-
- public ContentFeatureUnsupportedException(Throwable cause) {
- super(cause);
- }
-
- public ContentFeatureUnsupportedException(String message, Throwable cause) {
- super(message, cause);
- }
-
-}
+++ /dev/null
-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;
-
-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) {
- super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
- }
-
- private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
- Objects.requireNonNull(nsContext, "Namespace context cannot be null");
- Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
- String prefix = nsContext.getNamespaceURI(namespaceURI);
- if (prefix == null)
- throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
- return prefix;
- }
-
- ContentName(String namespaceURI, String localPart, String prefix) {
- super(namespaceURI, localPart, prefix);
- }
-
- public ContentName(String localPart) {
- super(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());
- }
-
- 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);
- }
-
- /*
- * UTILITIES
- */
-
- 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();
-
- String getNamespaceURI();
-
- String getDefaultPrefix();
-
- @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;
-
-/** 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);
- }
-
-
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-import java.util.Locale;
-import java.util.function.Supplier;
-
-/**
- * A content repository is an actually running implementation of various kind of
- * content system. It allows a pre-authenticated caller to open a session.
- */
-public interface ContentRepository extends Supplier<ContentSession> {
- ContentSession get(Locale locale);
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-/**
- * When there is a problem the underlying resources, typically IO, network, DB
- * access, etc.
- */
-public class ContentResourceException extends IllegalStateException {
- private static final long serialVersionUID = -2850145213683756996L;
-
- public ContentResourceException() {
- }
-
- public ContentResourceException(String s) {
- super(s);
- }
-
- public ContentResourceException(Throwable cause) {
- super(cause);
- }
-
- public ContentResourceException(String message, Throwable cause) {
- super(message, cause);
- }
-
-}
+++ /dev/null
-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 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();
- }
-
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-public interface ContentStore {
-
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-import java.io.PrintStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.List;
-import java.util.function.BiConsumer;
-
-import javax.xml.namespace.QName;
-
-public class ContentUtils {
- public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
- traverse(content, doIt, 0);
- }
-
- public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
- doIt.accept(content, currentDepth);
- int nextDepth = currentDepth + 1;
- for (Content child : content) {
- traverse(child, doIt, nextDepth);
- }
- }
-
- public static void print(Content content, PrintStream out, int depth, boolean printText) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < depth; i++) {
- sb.append(" ");
- }
- String prefix = sb.toString();
- out.println(prefix + content.getName());
- for (QName key : content.keySet()) {
- out.println(prefix + " " + key + "=" + content.get(key));
- }
- if (printText) {
- if (content.hasText()) {
- out.println("<![CDATA[" + content.getText().trim() + "]]>");
- }
- }
- }
-
- public static URI bytesToDataURI(byte[] arr) {
- String base64Str = Base64.getEncoder().encodeToString(arr);
- try {
- final String PREFIX = "data:application/octet-stream;base64,";
- return new URI(PREFIX + base64Str);
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
- }
-
- }
-
- public static byte[] bytesFromDataURI(URI uri) {
- if (!"data".equals(uri.getScheme()))
- throw new IllegalArgumentException("URI must have 'data' as a scheme");
- String schemeSpecificPart = uri.getSchemeSpecificPart();
- int commaIndex = schemeSpecificPart.indexOf(',');
- String prefix = schemeSpecificPart.substring(0, commaIndex);
- List<String> info = Arrays.asList(prefix.split(";"));
- if (!info.contains("base64"))
- throw new IllegalArgumentException("URI must specify base64");
-
- String base64Str = schemeSpecificPart.substring(commaIndex + 1);
- return Base64.getDecoder().decode(base64Str);
-
- }
-
- public static <T> boolean isString(T t) {
- return t instanceof String;
- }
-
- /** Singleton. */
- private ContentUtils() {
-
- }
-}
+++ /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;
-
-import javax.xml.XMLConstants;
-
-/**
- * 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 implements ContentNameSupplier {
- 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)
- DATE_TIME(Instant.class, new InstantFormatter()), //
- UUID(UUID.class, new UuidFormatter()), //
- ANY_URI(URI.class, new UriFormatter()), //
- STRING(String.class, new StringFormatter()), //
- ;
-
- private final Class<?> clss;
- private final AttributeFormatter<?> formatter;
-
- private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
- this.clss = clss;
- this.formatter = formatter;
- }
-
- public Class<?> getClss() {
- return clss;
- }
-
- public AttributeFormatter<?> getFormatter() {
- return formatter;
- }
-
- @Override
- public String getDefaultPrefix() {
- if (equals(UUID))
- return CrName.CR_DEFAULT_PREFIX;
- else
- return "xs";
- }
-
- @Override
- public String getNamespaceURI() {
- if (equals(UUID))
- return CrName.CR_NAMESPACE_URI;
- else
- return XMLConstants.W3C_XML_SCHEMA_NS_URI;
- }
-
- 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 DATE_TIME.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) ANY_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;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-/** Standard names. */
-public enum CrName implements ContentNameSupplier {
-
- /*
- * TYPES
- */
- COLLECTION, // a collection type
-
- /*
- * ATTRIBUTES
- */
- UUID, // the UUID of a content
-
- /*
- * ATTRIBUTES FROM FILE SEMANTICS
- */
- CREATION_TIME, //
- LAST_MODIFIED_TIME, //
- SIZE, //
- FILE_KEY, //
- OWNER, //
- GROUP, //
- PERMISSIONS, //
-
- /*
- * CONTENT NAMES
- */
- ROOT,
-
- //
- ;
-
- 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() {
- value = toContentName();
- }
-
- @Override
- public ContentName get() {
- return value;
- }
-
- @Override
- public String getNamespaceURI() {
- return CR_NAMESPACE_URI;
- }
-
- @Override
- public String getDefaultPrefix() {
- return CR_DEFAULT_PREFIX;
- }
-
-}
+++ /dev/null
-package org.argeo.api.gcr.fs;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.WatchEvent.Kind;
-import java.nio.file.WatchEvent.Modifier;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-public abstract class AbstractFsPath<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
- private final FS fs;
- /** null for non absolute paths */
- private final ST fileStore;
-
- private final String[] segments;// null means root
- private final boolean absolute;
-
- private final String separator;
-
- // optim
- private final int hashCode;
-
- public AbstractFsPath(FS filesSystem, String path) {
- if (path == null)
- throw new IllegalArgumentException("Path cannot be null");
- this.fs = filesSystem;
- this.separator = fs.getSeparator();
- // TODO deal with both path and separator being empty strings
- if (path.equals(separator)) {// root
- this.segments = null;
- this.absolute = true;
- this.hashCode = 0;
- this.fileStore = fs.getBaseFileStore();
- return;
- } else if (path.equals("")) {// empty path
- this.segments = new String[] { "" };
- this.absolute = false;
- this.hashCode = "".hashCode();
- this.fileStore = null;
- return;
- }
-
- this.absolute = path.startsWith(toStringRoot());
-
- String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
- path.endsWith(separator) ? path.length() - separator.length() : path.length());
- this.segments = trimmedPath.split(separator);
- // clean up
- for (int i = 0; i < this.segments.length; i++) {
- this.segments[i] = cleanUpSegment(this.segments[i]);
- }
- this.hashCode = this.segments[this.segments.length - 1].hashCode();
-
- this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
- }
-
- protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
- this.segments = segments;
- this.absolute = absolute;
- this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
- this.separator = filesSystem.getSeparator();
-// super(path, path == null ? true : absolute, filesSystem.getSeparator());
-// assert path == null ? absolute == true : true;
- this.fs = filesSystem;
-// this.path = path;
-// this.absolute = path == null ? true : absolute;
- if (isAbsolute() && fileStore == null)
- throw new IllegalArgumentException("Absolute path requires a file store");
- if (!isAbsolute() && fileStore != null)
- throw new IllegalArgumentException("A file store should not be provided for a relative path");
- this.fileStore = fileStore;
- assert !(absolute && fileStore == null);
- }
-
- protected Path retrieve(String path) {
- return getFileSystem().getPath(path);
- }
-
- @Override
- public FS getFileSystem() {
- return fs;
- }
-
- public ST getFileStore() {
- return fileStore;
- }
-
- @Override
- public boolean isAbsolute() {
- return absolute;
- }
-
- @Override
- public URI toUri() {
- try {
- return new URI(fs.provider().getScheme(), toString(), null);
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Cannot create URI for " + toString(), e);
- }
- }
-
- @Override
- public Path toAbsolutePath() {
- if (isAbsolute())
- return this;
- // FIXME it doesn't seem right
- return newInstance(getSegments(), true);
- }
-
- @Override
- public Path toRealPath(LinkOption... options) throws IOException {
- return this;
- }
-
- @Override
- public File toFile() {
- throw new UnsupportedOperationException();
- }
-
- /*
- * PATH OPERATIONS
- */
- public final Path resolveSibling(Path other) {
- if (other == null)
- throw new NullPointerException();
- Path parent = getParent();
- return (parent == null) ? other : parent.resolve(other);
- }
-
- @Override
- public final Path resolveSibling(String other) {
- return resolveSibling(getFileSystem().getPath(other));
- }
-
- public final Path resolve(String other) {
- return resolve(retrieve(other));
- }
-
- public boolean startsWith(Path other) {
- return toString().startsWith(other.toString());
- }
-
- public boolean endsWith(Path other) {
- return toString().endsWith(other.toString());
- }
-
- @Override
- public Path normalize() {
- // always normalized
- return this;
- }
-
- @Override
- public final Iterator<Path> iterator() {
- return new Iterator<Path>() {
- private int i = 0;
-
- @Override
- public boolean hasNext() {
- return (i < getNameCount());
- }
-
- @Override
- public Path next() {
- if (i < getNameCount()) {
- Path result = getName(i);
- i++;
- return result;
- } else {
- throw new NoSuchElementException();
- }
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-
- @Override
- public int compareTo(Path other) {
- return toString().compareTo(other.toString());
- }
-
- public Path resolve(Path other) {
- AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
- if (otherPath.isAbsolute())
- return other;
- String[] newPath;
- if (isRoot()) {
- newPath = new String[otherPath.segments.length];
- System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
- } else {
- newPath = new String[segments.length + otherPath.segments.length];
- System.arraycopy(segments, 0, newPath, 0, segments.length);
- System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
- }
- if (!absolute)
- return newInstance(newPath, absolute);
- else {
- return newInstance(toString(newPath));
- }
- }
-
- public Path relativize(Path other) {
- if (equals(other))
- return newInstance("");
- if (other.toString().startsWith(this.toString())) {
- String p1 = toString();
- String p2 = other.toString();
- String relative = p2.substring(p1.length(), p2.length());
- if (relative.charAt(0) == '/')
- relative = relative.substring(1);
- return newInstance(relative);
- }
- throw new IllegalArgumentException(other + " cannot be relativized against " + this);
- }
-
- /*
- * FACTORIES
- */
- protected abstract AbstractFsPath<FS, ST> newInstance(String path);
-
- protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
-
- /*
- * CUSTOMISATIONS
- */
- protected String toStringRoot() {
- return separator;
- }
-
- protected String cleanUpSegment(String segment) {
- return segment;
- }
-
- protected boolean isRoot() {
- return segments == null;
- }
-
- protected boolean isEmpty() {
- return segments.length == 1 && "".equals(segments[0]);
- }
-
- /*
- * PATH OPERATIONS
- */
- public AbstractFsPath<FS, ST> getRoot() {
- return newInstance(toStringRoot());
- }
-
- public AbstractFsPath<FS, ST> getParent() {
- if (isRoot())
- return null;
- // FIXME empty path?
- if (segments.length == 1)// first level
- return newInstance(toStringRoot());
- String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
- if (!absolute)
- return newInstance(parentPath, absolute);
- else
- return newInstance(toString(parentPath));
- }
-
- public AbstractFsPath<FS, ST> getFileName() {
- if (isRoot())
- return null;
- return newInstance(segments[segments.length - 1]);
- }
-
- public int getNameCount() {
- if (isRoot())
- return 0;
- return segments.length;
- }
-
- public AbstractFsPath<FS, ST> getName(int index) {
- if (isRoot())
- return null;
- return newInstance(segments[index]);
- }
-
- public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
- if (isRoot())
- return null;
- String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
- return newInstance(parentPath, false);
- }
-
- public boolean startsWith(String other) {
- return toString().startsWith(other);
- }
-
- public boolean endsWith(String other) {
- return toString().endsWith(other);
- }
-
- /*
- * UTILITIES
- */
- protected String toString(String[] path) {
- if (isRoot())
- return toStringRoot();
- StringBuilder sb = new StringBuilder();
- if (isAbsolute())
- sb.append(separator);
- for (int i = 0; i < path.length; i++) {
- if (i != 0)
- sb.append(separator);
- sb.append(path[i]);
- }
- return sb.toString();
- }
-
- @Override
- public String toString() {
- return toString(segments);
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof AbstractFsPath))
- return false;
- AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
-
- if (isRoot()) {// root
- if (other.isRoot())// root
- return true;
- else
- return false;
- } else {
- if (other.isRoot())// root
- return false;
- }
- // non root
- if (segments.length != other.segments.length)
- return false;
- for (int i = 0; i < segments.length; i++) {
- if (!segments[i].equals(other.segments[i]))
- return false;
- }
- return true;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return newInstance(toString());
- }
-
- /*
- * GETTERS / SETTERS
- */
- protected String[] getSegments() {
- return segments;
- }
-
- protected String getSeparator() {
- return separator;
- }
-
- /*
- * UNSUPPORTED
- */
- @Override
- public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
- throw new UnsupportedOperationException();
- }
-
-}
+++ /dev/null
-package org.argeo.api.gcr.fs;
-
-import java.nio.file.FileStore;
-
-public abstract class AbstractFsStore extends FileStore {
-
-}
+++ /dev/null
-package org.argeo.api.gcr.fs;
-
-import java.nio.file.FileSystem;
-
-public abstract class AbstractFsSystem<ST extends AbstractFsStore> extends FileSystem {
- public abstract ST getBaseFileStore();
-
- public abstract ST getFileStore(String path);
-}
+++ /dev/null
-package org.argeo.api.gcr.spi;
-
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-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<QName, Object> implements Content {
-
- /*
- * ATTRIBUTES OPERATIONS
- */
- protected abstract Iterable<QName> keys();
-
- protected abstract void removeAttr(QName key);
-
- @Override
- public Set<Entry<QName, Object>> entrySet() {
- Set<Entry<QName, Object>> result = new AttrSet();
- return result;
- }
-
- @Override
- public Class<?> getType(QName key) {
- return String.class;
- }
-
- @Override
- public boolean isMultiple(QName key) {
- return false;
- }
-
- @Override
- public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
- Object value = get(key);
- if (value == null)
- return null;
- if (value instanceof List) {
- try {
- List<A> res = (List<A>) value;
- return Optional.of(res);
- } catch (ClassCastException e) {
- List<A> res = new ArrayList<>();
- List<?> lst = (List<?>) value;
- try {
- for (Object o : lst) {
- A item = (A) o;
- res.add(item);
- }
- return Optional.of(res);
- } catch (ClassCastException e1) {
- return Optional.empty();
- }
- }
- } else {// singleton
- try {
- A res = (A) value;
- return Optional.of(Collections.singletonList(res));
- } catch (ClassCastException e) {
- return Optional.empty();
- }
- }
- }
-
- /*
- * CONTENT OPERATIONS
- */
-
- @Override
- public String getPath() {
- List<Content> ancestors = new ArrayList<>();
- collectAncestors(ancestors, this);
- StringBuilder path = new StringBuilder();
- for (Content c : ancestors) {
- QName name = c.getName();
- // FIXME
- if (!CrName.ROOT.get().equals(name))
- path.append('/').append(name);
- }
- return path.toString();
- }
-
- private void collectAncestors(List<Content> ancestors, Content content) {
- if (content == null)
- return;
- ancestors.add(0, content);
- collectAncestors(ancestors, content.getParent());
- }
-
- /*
- * UTILITIES
- */
- protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
- // check whether clss is Object.class
- return clss.isAssignableFrom(Object.class);
- }
-
- @Override
- public String toString() {
- return "content " + getPath();
- }
-
- /*
- * SUB CLASSES
- */
-
- class AttrSet extends AbstractSet<Entry<QName, Object>> {
-
- @Override
- public Iterator<Entry<QName, Object>> iterator() {
- final Iterator<QName> keys = keys().iterator();
- Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
-
- QName key = null;
-
- @Override
- public boolean hasNext() {
- return keys.hasNext();
- }
-
- @Override
- public Entry<QName, Object> next() {
- key = keys.next();
- // TODO check type
- Optional<?> value = get(key, Object.class);
- assert !value.isEmpty();
- AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
- return entry;
- }
-
- @Override
- public void remove() {
- if (key != null) {
- AbstractContent.this.removeAttr(key);
- } else {
- throw new IllegalStateException("Iteration has not started");
- }
- }
-
- };
- return it;
- }
-
- @Override
- public int size() {
- int count = 0;
- for (QName key : keys()) {
- count++;
- }
- return count;
- }
-
- }
-}
+++ /dev/null
-package org.argeo.api.gcr.spi;
-
-import org.argeo.api.gcr.Content;
-
-public interface ContentProvider {
-
- 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();
- }
-
-}
--- /dev/null
+package org.argeo.cms.jcr.acr;
+
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Optional;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+public class JcrContent extends AbstractContent {
+ private Node jcrNode;
+
+ 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 QName getName() {
+ return session.parsePrefixedName(Jcr.getName(jcrNode));
+ }
+
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ if (isDefaultAttrTypeRequested(clss)) {
+ return Optional.of((A) get(jcrNode, key.toString()));
+ }
+ return Optional.of((A) Jcr.get(jcrNode, key.toString()));
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ try {
+ return new JcrContentIterator(jcrNode.getNodes());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot list children of " + jcrNode, e);
+ }
+ }
+
+ @Override
+ protected Iterable<QName> keys() {
+ return new Iterable<QName>() {
+
+ @Override
+ public Iterator<QName> iterator() {
+ try {
+ PropertyIterator propertyIterator = jcrNode.getProperties();
+ return new JcrKeyIterator(provider, propertyIterator);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrive properties from " + jcrNode, e);
+ }
+ }
+ };
+ }
+
+ public Node getJcrNode() {
+ return jcrNode;
+ }
+
+ /** Cast to a standard Java object. */
+ static Object get(Node node, String property) {
+ try {
+ Value value = node.getProperty(property).getValue();
+ switch (value.getType()) {
+ case PropertyType.STRING:
+ return value.getString();
+ case PropertyType.DOUBLE:
+ return (Double) value.getDouble();
+ case PropertyType.LONG:
+ return (Long) value.getLong();
+ case PropertyType.BOOLEAN:
+ return (Boolean) value.getBoolean();
+ case PropertyType.DATE:
+ Calendar calendar = value.getDate();
+ return calendar.toInstant();
+ case PropertyType.BINARY:
+ throw new IllegalArgumentException("Binary is not supported as an attribute");
+ default:
+ return value.getString();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
+ }
+ }
+
+ 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(NodeIterator nodeIterator) {
+ this.nodeIterator = nodeIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nodeIterator.hasNext();
+ }
+
+ @Override
+ public Content next() {
+ current = new JcrContent(session, provider, nodeIterator.nextNode());
+ return current;
+ }
+
+ @Override
+ public void remove() {
+ if (current != null) {
+ Jcr.remove(current.getJcrNode());
+ }
+ }
+
+ }
+
+ @Override
+ public Content getParent() {
+ return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
+ }
+
+ @Override
+ public Content add(QName name, QName... classes) {
+ if (classes.length > 0) {
+ 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());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add child to " + getJcrNode(), e);
+ }
+ }
+
+ } else {
+ Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
+ }
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ Jcr.remove(getJcrNode());
+ }
+
+ @Override
+ protected void removeAttr(QName key) {
+ Property property = Jcr.getProperty(getJcrNode(), key.toString());
+ if (property != null) {
+ try {
+ property.remove();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
+ }
+ }
+
+ }
+
+ class JcrKeyIterator implements Iterator<QName> {
+ private final JcrContentProvider contentSession;
+ private final PropertyIterator propertyIterator;
+
+ protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
+ this.contentSession = contentSession;
+ this.propertyIterator = propertyIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return propertyIterator.hasNext();
+ }
+
+ @Override
+ public QName next() {
+ Property property = null;
+ try {
+ property = propertyIterator.nextProperty();
+ // TODO map standard property names
+ return session.parsePrefixedName(property.getName());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property, null);
+ }
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.acr;
+
+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.acr.Content;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+public class JcrContentProvider implements ContentProvider, NamespaceContext {
+ private Repository jcrRepository;
+ private Session adminSession;
+
+ public void init() {
+ adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
+ }
+
+ public void destroy() {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+
+ public void setJcrRepository(Repository jcrRepository) {
+ this.jcrRepository = jcrRepository;
+ }
+
+ @Override
+ public Content get(ProvidedSession session, String mountPath, String relativePath) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /*
+ * NAMESPACE CONTEXT
+ */
+ @Override
+ 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);
+ }
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.jcr.gcr;
-
-import java.util.Calendar;
-import java.util.Iterator;
-import java.util.Optional;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.gcr.Content;
-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 Node jcrNode;
-
- 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 QName getName() {
- return session.parsePrefixedName(Jcr.getName(jcrNode));
- }
-
- @Override
- public <A> Optional<A> get(QName key, Class<A> clss) {
- if (isDefaultAttrTypeRequested(clss)) {
- return Optional.of((A) get(jcrNode, key.toString()));
- }
- return Optional.of((A) Jcr.get(jcrNode, key.toString()));
- }
-
- @Override
- public Iterator<Content> iterator() {
- try {
- return new JcrContentIterator(jcrNode.getNodes());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot list children of " + jcrNode, e);
- }
- }
-
- @Override
- protected Iterable<QName> keys() {
- return new Iterable<QName>() {
-
- @Override
- public Iterator<QName> iterator() {
- try {
- PropertyIterator propertyIterator = jcrNode.getProperties();
- return new JcrKeyIterator(provider, propertyIterator);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrive properties from " + jcrNode, e);
- }
- }
- };
- }
-
- public Node getJcrNode() {
- return jcrNode;
- }
-
- /** Cast to a standard Java object. */
- static Object get(Node node, String property) {
- try {
- Value value = node.getProperty(property).getValue();
- switch (value.getType()) {
- case PropertyType.STRING:
- return value.getString();
- case PropertyType.DOUBLE:
- return (Double) value.getDouble();
- case PropertyType.LONG:
- return (Long) value.getLong();
- case PropertyType.BOOLEAN:
- return (Boolean) value.getBoolean();
- case PropertyType.DATE:
- Calendar calendar = value.getDate();
- return calendar.toInstant();
- case PropertyType.BINARY:
- throw new IllegalArgumentException("Binary is not supported as an attribute");
- default:
- return value.getString();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
- }
- }
-
- 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(NodeIterator nodeIterator) {
- this.nodeIterator = nodeIterator;
- }
-
- @Override
- public boolean hasNext() {
- return nodeIterator.hasNext();
- }
-
- @Override
- public Content next() {
- current = new JcrContent(session, provider, nodeIterator.nextNode());
- return current;
- }
-
- @Override
- public void remove() {
- if (current != null) {
- Jcr.remove(current.getJcrNode());
- }
- }
-
- }
-
- @Override
- public Content getParent() {
- return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
- }
-
- @Override
- public Content add(QName name, QName... classes) {
- if (classes.length > 0) {
- 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());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add child to " + getJcrNode(), e);
- }
- }
-
- } else {
- Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
- }
- return null;
- }
-
- @Override
- public void remove() {
- Jcr.remove(getJcrNode());
- }
-
- @Override
- protected void removeAttr(QName key) {
- Property property = Jcr.getProperty(getJcrNode(), key.toString());
- if (property != null) {
- try {
- property.remove();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
- }
- }
-
- }
-
- class JcrKeyIterator implements Iterator<QName> {
- private final JcrContentProvider contentSession;
- private final PropertyIterator propertyIterator;
-
- protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
- this.contentSession = contentSession;
- this.propertyIterator = propertyIterator;
- }
-
- @Override
- public boolean hasNext() {
- return propertyIterator.hasNext();
- }
-
- @Override
- public QName next() {
- Property property = null;
- try {
- property = propertyIterator.nextProperty();
- // TODO map standard property names
- return session.parsePrefixedName(property.getName());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property, null);
- }
- }
-
- }
-}
+++ /dev/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, NamespaceContext {
- private Repository jcrRepository;
- private Session adminSession;
-
- public void init() {
- adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
- }
-
- public void destroy() {
- JcrUtils.logoutQuietly(adminSession);
- }
-
- public void setJcrRepository(Repository jcrRepository) {
- this.jcrRepository = jcrRepository;
- }
-
- @Override
- public Content get(ProvidedSession session, String mountPath, String relativePath) {
- // TODO Auto-generated method stub
- return null;
- }
-
- /*
- * NAMESPACE CONTEXT
- */
- @Override
- 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 javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
-import org.argeo.api.gcr.fs.AbstractFsStore;
-import org.argeo.api.gcr.fs.AbstractFsSystem;
+import org.argeo.api.acr.fs.AbstractFsStore;
+import org.argeo.api.acr.fs.AbstractFsSystem;
import org.argeo.jcr.Jcr;
import org.argeo.jcr.JcrUtils;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
-import org.argeo.api.gcr.fs.AbstractFsPath;
+import org.argeo.api.acr.fs.AbstractFsPath;
/** A {@link Path} which contains a reference to a JCR {@link Node}. */
public class JcrPath extends AbstractFsPath<JcrFileSystem, WorkspaceFileStore> {
import javax.jcr.Session;
import javax.jcr.Workspace;
-import org.argeo.api.gcr.fs.AbstractFsStore;
+import org.argeo.api.acr.fs.AbstractFsStore;
import org.argeo.jcr.JcrUtils;
/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
import javax.xml.namespace.QName;
-import org.argeo.api.gcr.Content;
-import org.argeo.cms.gcr.fs.FsContentProvider;
+import org.argeo.api.acr.Content;
+import org.argeo.cms.acr.fs.FsContentProvider;
import org.argeo.cms.swt.CmsSwtUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
package org.argeo.cms.swt.gcr;
+import org.argeo.api.acr.Content;
import org.argeo.api.cms.MvcProvider;
-import org.argeo.api.gcr.Content;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
<artifactId>org.argeo.cms.tp</artifactId>
<packaging>jar</packaging>
<name>CMS Third Parties Adapters</name>
- <description>Workarounds or trivial implementations from some third parties, typically logging</description>
+ <description>Workarounds or trivial implementations of some third parties, typically logging</description>
<dependencies>
<dependency>
<groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.api</artifactId>
+ <artifactId>org.argeo.api.cms</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.api</artifactId>
+ <artifactId>org.argeo.api.cms</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.api.acr</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
<artifactId>org.argeo.util</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
-<!-- <dependency> -->
-<!-- <groupId>org.argeo.commons</groupId> -->
-<!-- <artifactId>org.argeo.core</artifactId> -->
-<!-- <version>2.3-SNAPSHOT</version> -->
-<!-- </dependency> -->
-<!-- <dependency> -->
-<!-- <groupId>org.argeo.commons</groupId> -->
-<!-- <artifactId>org.argeo.maintenance</artifactId> -->
-<!-- <version>2.3-SNAPSHOT</version> -->
-<!-- </dependency> -->
</dependencies>
</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.acr;
+
+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.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+
+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());
+ }
+
+ @Override
+ public ContentSession get(Locale locale) {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ return new CmsContentSession(subject, locale);
+ }
+
+ public void addProvider(String base, ContentProvider provider) {
+ partitions.put(base, provider);
+ }
+
+ 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;
+
+ public CmsContentSession(Subject subject, Locale locale) {
+ this.subject = subject;
+ this.locale = locale;
+ }
+
+ @Override
+ public Content get(String path) {
+ 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
+ public Subject getSubject() {
+ return subject;
+ }
+
+ @Override
+ public Locale getLocale() {
+ 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);
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.util.FsUtils;
+
+public class FsContent extends AbstractContent implements ProvidedContent {
+ private final static String USER_ = "user:";
+
+ private static final Map<QName, String> BASIC_KEYS;
+ private static final Map<QName, String> POSIX_KEYS;
+ static {
+ 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 ProvidedSession session;
+ private final FsContentProvider provider;
+ private final Path path;
+ private final boolean isRoot;
+ private final QName name;
+
+ 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() {
+ return path.getFileSystem().supportedFileAttributeViews().contains("posix");
+ }
+
+ @Override
+ public QName getName() {
+ return name;
+ }
+
+ /*
+ * ATTRIBUTES
+ */
+
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ Object value;
+ try {
+ // We need to add user: when accessing via Files#getAttribute
+ value = Files.getAttribute(path, toFsAttributeKey(key));
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
+ }
+ A res = null;
+ if (value instanceof FileTime) {
+ if (clss.isAssignableFrom(FileTime.class))
+ res = (A) value;
+ Instant instant = ((FileTime) value).toInstant();
+ if (Object.class.isAssignableFrom(clss)) {// plain object requested
+ res = (A) instant;
+ }
+ // TODO perform trivial file conversion to other formats
+ }
+ if (value instanceof byte[]) {
+ res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
+ }
+ if (res == null)
+ try {
+ res = (A) value;
+ } catch (ClassCastException e) {
+ return Optional.empty();
+ }
+ return Optional.of(res);
+ }
+
+ @Override
+ 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(session.parsePrefixedName(name));
+ }
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot list attributes for " + path, e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected void removeAttr(QName key) {
+ UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+ try {
+ udfav.delete(session.toPrefixedName(key));
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+ }
+ }
+
+ @Override
+ 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(session.toPrefixedName(key), bb);
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+ }
+ return previous;
+ }
+
+ protected String toFsAttributeKey(QName key) {
+ if (POSIX_KEYS.containsKey(key))
+ return POSIX_KEYS.get(key);
+ else
+ return USER_ + session.toPrefixedName(key);
+ }
+
+ /*
+ * CONTENT OPERATIONS
+ */
+ @Override
+ public Iterator<Content> iterator() {
+ if (Files.isDirectory(path)) {
+ try {
+ return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot list " + path, e);
+ }
+ } else {
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public Content add(QName name, QName... classes) {
+ try {
+ 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(this, newPath);
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot create new content", e);
+ }
+ }
+
+ @Override
+ public void remove() {
+ FsUtils.delete(path);
+ }
+
+ @Override
+ public Content getParent() {
+ if (isRoot)
+ return null;// TODO deal with mounts
+ return new FsContent(this, path.getParent());
+ }
+
+ /*
+ * ACCESSORS
+ */
+ @Override
+ public ProvidedSession getSession() {
+ return session;
+ }
+
+ @Override
+ public FsContentProvider getProvider() {
+ return provider;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+
+public class FsContentProvider implements ContentProvider {
+ private final Path rootPath;
+
+ public FsContentProvider(Path rootPath) {
+ super();
+ this.rootPath = rootPath;
+ }
+
+ boolean isRoot(Path path) {
+ try {
+ return Files.isSameFile(rootPath, path);
+ } catch (IOException e) {
+ throw new ContentResourceException(e);
+ }
+ }
+
+ @Override
+ public Content get(ProvidedSession session, String mountPath, String relativePath) {
+ return new FsContent(session, this, rootPath.resolve(relativePath));
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.xml;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.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 ProvidedContent {
+
+ private final ProvidedSession session;
+ private final DomContentProvider provider;
+ private final Element element;
+
+// private String text = null;
+ private Boolean hasText = null;
+
+ public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
+ this.session = session;
+ this.provider = contentProvider;
+ this.element = element;
+ }
+
+ public DomContent(DomContent context, Element element) {
+ this(context.getSession(), context.getProvider(), element);
+ }
+
+ @Override
+ 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<QName> keys() {
+ // TODO implement an iterator?
+ Set<QName> result = new HashSet<>();
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Attr attr = (Attr) attributes.item(i);
+ QName key = toQName(attr);
+ result.add(key);
+ }
+ return result;
+ }
+
+ @Override
+ public <A> Optional<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 Optional.of((A) value);
+ else
+ return Optional.empty();
+ } 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;
+ if (hasText != null)
+ return hasText;
+ NodeList nodeList = element.getChildNodes();
+ if (nodeList.getLength() > 1) {
+ hasText = false;
+ return hasText;
+ }
+ nodes: for (int i = 0; i < nodeList.getLength(); i++) {
+ Node node = nodeList.item(i);
+ if (node instanceof Text) {
+ Text text = (Text) node;
+ if (!text.isElementContentWhitespace()) {
+ hasText = true;
+ break nodes;
+ }
+ }
+ }
+ if (hasText == null)
+ hasText = false;
+ return hasText;
+// if (text != null)
+// return true;
+// text = element.getTextContent();
+// return text != null;
+ }
+
+ @Override
+ public String getText() {
+ if (hasText())
+ return element.getTextContent();
+ else
+ 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();
+ if (parent == null)
+ return null;
+ if (!(parent instanceof Element))
+ throw new IllegalStateException("Parent is not an element");
+ return new DomContent(this, (Element) parent);
+ }
+
+ @Override
+ public Content add(QName name, QName... classes) {
+ // TODO consider classes
+ 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
+ public void remove() {
+ // TODO make it more robust
+ element.getParentNode().removeChild(element);
+
+ }
+
+ @Override
+ 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;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.xml;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+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.acr.Content;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+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(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 /");
+
+ 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 String getNamespaceURI(String prefix) {
+ return document.lookupNamespaceURI(prefix);
+ }
+
+ @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();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.xml;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+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(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+ this.session = session;
+ this.provider = provider;
+ this.nodeList = nodeList;
+
+ this.length = nodeList.getLength();
+ this.currentIndex = 0;
+ this.nextElement = findNext();
+ }
+
+ private Element findNext() {
+ while (currentIndex < length) {
+ Node node = nodeList.item(currentIndex);
+ if (node instanceof Element) {
+ return (Element) node;
+ }
+ currentIndex++;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextElement != null;
+ }
+
+ @Override
+ public Content next() {
+ if (nextElement == null)
+ throw new NoSuchElementException();
+ DomContent result = new DomContent(session, provider, nextElement);
+ currentIndex++;
+ nextElement = findNext();
+ return result;
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.gcr;
-
-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.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 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());
- }
-
- @Override
- public ContentSession get(Locale locale) {
- Subject subject = Subject.getSubject(AccessController.getContext());
- return new CmsContentSession(subject, locale);
- }
-
- public void addProvider(String base, ContentProvider provider) {
- partitions.put(base, provider);
- }
-
- 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;
-
- public CmsContentSession(Subject subject, Locale locale) {
- this.subject = subject;
- this.locale = locale;
- }
-
- @Override
- public Content get(String path) {
- 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
- public Subject getSubject() {
- return subject;
- }
-
- @Override
- public Locale getLocale() {
- 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);
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.cms.gcr.fs;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.UserDefinedFileAttributeView;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Optional;
-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.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 ProvidedContent {
- private final static String USER_ = "user:";
-
- private static final Map<QName, String> BASIC_KEYS;
- private static final Map<QName, String> POSIX_KEYS;
- static {
- 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 ProvidedSession session;
- private final FsContentProvider provider;
- private final Path path;
- private final boolean isRoot;
- private final QName name;
-
- 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() {
- return path.getFileSystem().supportedFileAttributeViews().contains("posix");
- }
-
- @Override
- public QName getName() {
- return name;
- }
-
- /*
- * ATTRIBUTES
- */
-
- @Override
- public <A> Optional<A> get(QName key, Class<A> clss) {
- Object value;
- try {
- // We need to add user: when accessing via Files#getAttribute
- value = Files.getAttribute(path, toFsAttributeKey(key));
- } catch (IOException e) {
- throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
- }
- A res = null;
- if (value instanceof FileTime) {
- if (clss.isAssignableFrom(FileTime.class))
- res = (A) value;
- Instant instant = ((FileTime) value).toInstant();
- if (Object.class.isAssignableFrom(clss)) {// plain object requested
- res = (A) instant;
- }
- // TODO perform trivial file conversion to other formats
- }
- if (value instanceof byte[]) {
- res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
- }
- if (res == null)
- try {
- res = (A) value;
- } catch (ClassCastException e) {
- return Optional.empty();
- }
- return Optional.of(res);
- }
-
- @Override
- 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(session.parsePrefixedName(name));
- }
- } catch (IOException e) {
- throw new ContentResourceException("Cannot list attributes for " + path, e);
- }
- }
- return result;
- }
-
- @Override
- protected void removeAttr(QName key) {
- UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
- try {
- udfav.delete(session.toPrefixedName(key));
- } catch (IOException e) {
- throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
- }
- }
-
- @Override
- 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(session.toPrefixedName(key), bb);
- } catch (IOException e) {
- throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
- }
- return previous;
- }
-
- protected String toFsAttributeKey(QName key) {
- if (POSIX_KEYS.containsKey(key))
- return POSIX_KEYS.get(key);
- else
- return USER_ + session.toPrefixedName(key);
- }
-
- /*
- * CONTENT OPERATIONS
- */
- @Override
- public Iterator<Content> iterator() {
- if (Files.isDirectory(path)) {
- try {
- return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
- } catch (IOException e) {
- throw new ContentResourceException("Cannot list " + path, e);
- }
- } else {
- return Collections.emptyIterator();
- }
- }
-
- @Override
- public Content add(QName name, QName... classes) {
- try {
- 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(this, newPath);
- } catch (IOException e) {
- throw new ContentResourceException("Cannot create new content", e);
- }
- }
-
- @Override
- public void remove() {
- FsUtils.delete(path);
- }
-
- @Override
- public Content getParent() {
- if (isRoot)
- return null;// TODO deal with mounts
- return new FsContent(this, path.getParent());
- }
-
- /*
- * ACCESSORS
- */
- @Override
- public ProvidedSession getSession() {
- return session;
- }
-
- @Override
- public FsContentProvider getProvider() {
- return provider;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.gcr.fs;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentResourceException;
-import org.argeo.api.gcr.spi.ContentProvider;
-import org.argeo.api.gcr.spi.ProvidedSession;
-
-public class FsContentProvider implements ContentProvider {
- private final Path rootPath;
-
- public FsContentProvider(Path rootPath) {
- super();
- this.rootPath = rootPath;
- }
-
- boolean isRoot(Path path) {
- try {
- return Files.isSameFile(rootPath, path);
- } catch (IOException e) {
- throw new ContentResourceException(e);
- }
- }
-
- @Override
- public Content get(ProvidedSession session, String mountPath, String relativePath) {
- return new FsContent(session, this, rootPath.resolve(relativePath));
- }
-}
+++ /dev/null
-package org.argeo.cms.gcr.xml;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Optional;
-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 ProvidedContent {
-
- private final ProvidedSession session;
- private final DomContentProvider provider;
- private final Element element;
-
-// private String text = null;
- private Boolean hasText = null;
-
- public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
- this.session = session;
- this.provider = contentProvider;
- this.element = element;
- }
-
- public DomContent(DomContent context, Element element) {
- this(context.getSession(), context.getProvider(), element);
- }
-
- @Override
- 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<QName> keys() {
- // TODO implement an iterator?
- Set<QName> result = new HashSet<>();
- NamedNodeMap attributes = element.getAttributes();
- for (int i = 0; i < attributes.getLength(); i++) {
- Attr attr = (Attr) attributes.item(i);
- QName key = toQName(attr);
- result.add(key);
- }
- return result;
- }
-
- @Override
- public <A> Optional<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 Optional.of((A) value);
- else
- return Optional.empty();
- } 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;
- if (hasText != null)
- return hasText;
- NodeList nodeList = element.getChildNodes();
- if (nodeList.getLength() > 1) {
- hasText = false;
- return hasText;
- }
- nodes: for (int i = 0; i < nodeList.getLength(); i++) {
- Node node = nodeList.item(i);
- if (node instanceof Text) {
- Text text = (Text) node;
- if (!text.isElementContentWhitespace()) {
- hasText = true;
- break nodes;
- }
- }
- }
- if (hasText == null)
- hasText = false;
- return hasText;
-// if (text != null)
-// return true;
-// text = element.getTextContent();
-// return text != null;
- }
-
- @Override
- public String getText() {
- if (hasText())
- return element.getTextContent();
- else
- 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();
- if (parent == null)
- return null;
- if (!(parent instanceof Element))
- throw new IllegalStateException("Parent is not an element");
- return new DomContent(this, (Element) parent);
- }
-
- @Override
- public Content add(QName name, QName... classes) {
- // TODO consider classes
- 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
- public void remove() {
- // TODO make it more robust
- element.getParentNode().removeChild(element);
-
- }
-
- @Override
- 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;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.gcr.xml;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-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.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, 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(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 /");
-
- 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 String getNamespaceURI(String prefix) {
- return document.lookupNamespaceURI(prefix);
- }
-
- @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();
- }
-
-}
+++ /dev/null
-package org.argeo.cms.gcr.xml;
-
-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;
-
-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(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
- this.session = session;
- this.provider = provider;
- this.nodeList = nodeList;
-
- this.length = nodeList.getLength();
- this.currentIndex = 0;
- this.nextElement = findNext();
- }
-
- private Element findNext() {
- while (currentIndex < length) {
- Node node = nodeList.item(currentIndex);
- if (node instanceof Element) {
- return (Element) node;
- }
- currentIndex++;
- }
- return null;
- }
-
- @Override
- public boolean hasNext() {
- return nextElement != null;
- }
-
- @Override
- public Content next() {
- if (nextElement == null)
- throw new NoSuchElementException();
- DomContent result = new DomContent(session, provider, nextElement);
- currentIndex++;
- nextElement = findNext();
- return result;
- }
-
-}
<!-- Base -->
<module>org.argeo.init</module>
<module>org.argeo.util</module>
- <!-- Eclipse -->
+ <!-- API -->
+ <module>org.argeo.api.acr</module>
+ <module>org.argeo.api.cms</module>
<!-- CMS -->
- <module>org.argeo.api</module>
<module>org.argeo.cms.tp</module>
<module>org.argeo.cms</module>
<module>org.argeo.cms.pgsql</module>