From: Mathieu Baudier Date: Sat, 22 Jan 2022 05:14:42 +0000 (+0100) Subject: Rename GCR to ACR. X-Git-Tag: argeo-commons-2.3.5~77 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=7d2a002f5dcfe8a8c7b29803b70d4b1aff265ed1 Rename GCR to ACR. --- diff --git a/dep/org.argeo.dep.cms.base/pom.xml b/dep/org.argeo.dep.cms.base/pom.xml index a46e8ec30..3a2af17f1 100644 --- a/dep/org.argeo.dep.cms.base/pom.xml +++ b/dep/org.argeo.dep.cms.base/pom.xml @@ -24,20 +24,15 @@ org.argeo.util 2.3-SNAPSHOT - - - - - - - - - - org.argeo.commons - org.argeo.api + org.argeo.api.acr + 2.3-SNAPSHOT + + + org.argeo.commons + org.argeo.api.cms 2.3-SNAPSHOT diff --git a/org.argeo.api.acr/.classpath b/org.argeo.api.acr/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/org.argeo.api.acr/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.api.acr/.project b/org.argeo.api.acr/.project new file mode 100644 index 000000000..d7a785e1d --- /dev/null +++ b/org.argeo.api.acr/.project @@ -0,0 +1,28 @@ + + + org.argeo.api.acr + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.api.acr/.settings/org.eclipse.jdt.core.prefs b/org.argeo.api.acr/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c9545f06a --- /dev/null +++ b/org.argeo.api.acr/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +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 diff --git a/org.argeo.api.acr/.settings/org.eclipse.pde.core.prefs b/org.argeo.api.acr/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..f29e940a0 --- /dev/null +++ b/org.argeo.api.acr/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/org.argeo.api.acr/bnd.bnd b/org.argeo.api.acr/bnd.bnd new file mode 100644 index 000000000..6e839c070 --- /dev/null +++ b/org.argeo.api.acr/bnd.bnd @@ -0,0 +1,4 @@ +Import-Package: \ +javax.security.* + +Export-Package: org.argeo.api.acr.* \ No newline at end of file diff --git a/org.argeo.api.acr/build.properties b/org.argeo.api.acr/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.api.acr/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.api.acr/pom.xml b/org.argeo.api.acr/pom.xml new file mode 100644 index 000000000..bc2383831 --- /dev/null +++ b/org.argeo.api.acr/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + org.argeo.commons + argeo-commons + 2.3-SNAPSHOT + .. + + org.argeo.api.acr + ACR API + jar + \ No newline at end of file diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java b/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java new file mode 100644 index 000000000..41600331a --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java @@ -0,0 +1,19 @@ +package org.argeo.api.acr; + +/** + * An attribute type MUST consistently parse a string to an object so that + * parse(obj.toString()).equals(obj) is verified. + * {@link #format(Object)} can be overridden to provide more efficient + * implementations but the returned + * String MUST be the same, that is format(obj).equals(obj.toString()) + * is verified. + */ +public interface AttributeFormatter { + /** 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(); + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java b/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java new file mode 100644 index 000000000..b83fe301e --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java @@ -0,0 +1,164 @@ +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 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()); + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java new file mode 100644 index 000000000..edcfaea10 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java @@ -0,0 +1,118 @@ +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, Map { + + QName getName(); + + String getPath(); + + Content getParent(); + + /* + * ATTRIBUTES OPERATIONS + */ + + Optional get(QName key, Class 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); + + Optional> getMultiple(QName key, Class clss); + + default List getMultiple(QName key) { + Class type; + try { + type = (Class) getType(key); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Requested type is not the default type"); + } + Optional> 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 adapt(Class clss) throws IllegalArgumentException { + throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName()); + } + + default C open(Class 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 get(Object key, Class clss) { +// return key != null ? get(key.toString(), clss) : get(null, clss); +// } + + /* + * EXPERIMENTAL UNSUPPORTED + */ + default boolean hasText() { + return false; + } + + default String getText() { + throw new UnsupportedOperationException(); + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java new file mode 100644 index 000000000..485caa9a4 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java @@ -0,0 +1,22 @@ +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); + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java new file mode 100644 index 000000000..341a3e297 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java @@ -0,0 +1,165 @@ +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 namespaceUuids = Collections.synchronizedMap(new TreeMap<>()); + private final static Map 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; + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java new file mode 100644 index 000000000..e3c721fef --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java @@ -0,0 +1,106 @@ +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, 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 getPrefixes(String namespaceURI) { + Iterator 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 getStandardPrefixes(String namespaceURI) { + String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI); + if (prefix == null) + return null; + return Collections.singleton(prefix).iterator(); + } + + static String getStandardNamespaceURI(String prefix) { + if (prefix == null) + throw new IllegalArgumentException("Prefix cannot be null"); + if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + return XMLConstants.XML_NS_URI; + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + return null; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java new file mode 100644 index 000000000..b86c92c1e --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java @@ -0,0 +1,16 @@ +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); + } + + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java new file mode 100644 index 000000000..3b2f156e0 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java @@ -0,0 +1,12 @@ +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 get(Locale locale); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java new file mode 100644 index 000000000..e418c4ff7 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java @@ -0,0 +1,25 @@ +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); + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java new file mode 100644 index 000000000..215bb9e22 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java @@ -0,0 +1,49 @@ +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(); + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java new file mode 100644 index 000000000..cda5d91f8 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java @@ -0,0 +1,5 @@ +package org.argeo.api.acr; + +public interface ContentStore { + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java new file mode 100644 index 000000000..2036c8622 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java @@ -0,0 +1,77 @@ +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 doIt) { + traverse(content, doIt, 0); + } + + public static void traverse(Content content, BiConsumer 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(""); + } + } + } + + 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 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 boolean isString(T t) { + return t instanceof String; + } + + /** Singleton. */ + private ContentUtils() { + + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java new file mode 100644 index 000000000..ffa28af0a --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java @@ -0,0 +1,201 @@ +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 + * String. + */ +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 CrAttributeType(Class clss, AttributeFormatter 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 { + + /** + * @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 { + @Override + public Integer parse(String str) throws NumberFormatException { + return Integer.parseInt(str); + } + } + + static class LongFormatter implements AttributeFormatter { + @Override + public Long parse(String str) throws NumberFormatException { + return Long.parseLong(str); + } + } + + static class DoubleFormatter implements AttributeFormatter { + + @Override + public Double parse(String str) throws NumberFormatException { + return Double.parseDouble(str); + } + } + + static class InstantFormatter implements AttributeFormatter { + + @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 { + + @Override + public UUID parse(String str) throws IllegalArgumentException { + return java.util.UUID.fromString(str); + } + } + + static class UriFormatter implements AttributeFormatter { + + @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 { + + @Override + public String parse(String str) { + return str; + } + + @Override + public String format(String obj) { + return obj; + } + + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java new file mode 100644 index 000000000..099be9fda --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java @@ -0,0 +1,58 @@ +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; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java new file mode 100644 index 000000000..fa95dafdd --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java @@ -0,0 +1,387 @@ +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, 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 iterator() { + return new Iterator() { + 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 newInstance(String path); + + protected abstract AbstractFsPath 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 getRoot() { + return newInstance(toStringRoot()); + } + + public AbstractFsPath 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 getFileName() { + if (isRoot()) + return null; + return newInstance(segments[segments.length - 1]); + } + + public int getNameCount() { + if (isRoot()) + return 0; + return segments.length; + } + + public AbstractFsPath getName(int index) { + if (isRoot()) + return null; + return newInstance(segments[index]); + } + + public AbstractFsPath 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(); + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java new file mode 100644 index 000000000..129476685 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java @@ -0,0 +1,7 @@ +package org.argeo.api.acr.fs; + +import java.nio.file.FileStore; + +public abstract class AbstractFsStore extends FileStore { + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java new file mode 100644 index 000000000..3bf10f6b6 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java @@ -0,0 +1,9 @@ +package org.argeo.api.acr.fs; + +import java.nio.file.FileSystem; + +public abstract class AbstractFsSystem extends FileSystem { + public abstract ST getBaseFileStore(); + + public abstract ST getFileStore(String path); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java new file mode 100644 index 000000000..6bfc7cd5f --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java @@ -0,0 +1,164 @@ +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 implements Content { + + /* + * ATTRIBUTES OPERATIONS + */ + protected abstract Iterable keys(); + + protected abstract void removeAttr(QName key); + + @Override + public Set> entrySet() { + Set> result = new AttrSet(); + return result; + } + + @Override + public Class getType(QName key) { + return String.class; + } + + @Override + public boolean isMultiple(QName key) { + return false; + } + + @Override + public Optional> getMultiple(QName key, Class clss) { + Object value = get(key); + if (value == null) + return null; + if (value instanceof List) { + try { + List res = (List) value; + return Optional.of(res); + } catch (ClassCastException e) { + List 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 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 ancestors, Content content) { + if (content == null) + return; + ancestors.add(0, content); + collectAncestors(ancestors, content.getParent()); + } + + /* + * UTILITIES + */ + protected boolean isDefaultAttrTypeRequested(Class clss) { + // check whether clss is Object.class + return clss.isAssignableFrom(Object.class); + } + + @Override + public String toString() { + return "content " + getPath(); + } + + /* + * SUB CLASSES + */ + + class AttrSet extends AbstractSet> { + + @Override + public Iterator> iterator() { + final Iterator keys = keys().iterator(); + Iterator> it = new Iterator>() { + + QName key = null; + + @Override + public boolean hasNext() { + return keys.hasNext(); + } + + @Override + public Entry next() { + key = keys.next(); + // TODO check type + Optional value = get(key, Object.class); + assert !value.isEmpty(); + AbstractMap.SimpleEntry 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; + } + + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java new file mode 100644 index 000000000..d83cf49c9 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java @@ -0,0 +1,9 @@ +package org.argeo.api.acr.spi; + +import org.argeo.api.acr.Content; + +public interface ContentProvider { + + Content get(ProvidedSession session, String mountPath, String relativePath); + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java new file mode 100644 index 000000000..d9fc781d0 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java @@ -0,0 +1,9 @@ +package org.argeo.api.acr.spi; + +import org.argeo.api.acr.Content; + +public interface ProvidedContent extends Content { + ProvidedSession getSession(); + + ContentProvider getProvider(); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java new file mode 100644 index 000000000..c3052c375 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java @@ -0,0 +1,6 @@ +package org.argeo.api.acr.spi; + +import org.argeo.api.acr.ContentRepository; + +public interface ProvidedRepository extends ContentRepository { +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java new file mode 100644 index 000000000..f90d67475 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java @@ -0,0 +1,68 @@ +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 findPrefixes(String namespaceURI); + + /** To be overridden for optimisation, as it will be called a lot */ + default String findPrefix(String namespaceURI) { + Set 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 getPrefixes(String namespaceURI) { + Iterator 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 prefixes = findPrefixes(namespaceURI); + assert prefixes != null; + return prefixes.iterator(); + } + +} diff --git a/org.argeo.api.cms/.classpath b/org.argeo.api.cms/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/org.argeo.api.cms/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.api.cms/.project b/org.argeo.api.cms/.project new file mode 100644 index 000000000..17631c896 --- /dev/null +++ b/org.argeo.api.cms/.project @@ -0,0 +1,28 @@ + + + org.argeo.api.cms + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.api.cms/META-INF/.gitignore b/org.argeo.api.cms/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/org.argeo.api.cms/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/org.argeo.api.cms/bnd.bnd b/org.argeo.api.cms/bnd.bnd new file mode 100644 index 000000000..51c4e663c --- /dev/null +++ b/org.argeo.api.cms/bnd.bnd @@ -0,0 +1,4 @@ +Import-Package: \ +javax.security.* + +Export-Package: org.argeo.api.cms.* \ No newline at end of file diff --git a/org.argeo.api.cms/build.properties b/org.argeo.api.cms/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.api.cms/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.api.cms/pom.xml b/org.argeo.api.cms/pom.xml new file mode 100644 index 000000000..b1cfca0d3 --- /dev/null +++ b/org.argeo.api.cms/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + org.argeo.commons + argeo-commons + 2.3-SNAPSHOT + .. + + org.argeo.api.cms + CMS API + jar + \ No newline at end of file diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java b/org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java new file mode 100644 index 000000000..63ee34853 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java @@ -0,0 +1,28 @@ +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(); + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java new file mode 100644 index 000000000..30b3d8100 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java @@ -0,0 +1,34 @@ +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; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java new file mode 100644 index 000000000..761191e5d --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java @@ -0,0 +1,31 @@ +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 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); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java new file mode 100644 index 000000000..55fcec56b --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java @@ -0,0 +1,7 @@ +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(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java new file mode 100644 index 000000000..decea3550 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java @@ -0,0 +1,46 @@ +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(); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java new file mode 100644 index 000000000..8fe9846f4 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java @@ -0,0 +1,126 @@ +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"; +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java new file mode 100644 index 000000000..fa26b253a --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java @@ -0,0 +1,26 @@ +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 getLocales(); + + Long getAvailableSince(); + + + /** Mark this group as a workgroup */ + void createWorkgroup(String groupDn); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java new file mode 100644 index 000000000..5893d2ec5 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java @@ -0,0 +1,11 @@ +package org.argeo.api.cms; + +import java.util.Dictionary; + +/** A configured node deployment. */ +public interface CmsDeployment { + + void addFactoryDeployConfig(String factoryPid, Dictionary props); + + Dictionary getProps(String factoryPid, String cn); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java new file mode 100644 index 000000000..2deca018e --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java @@ -0,0 +1,57 @@ +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; + } + }; + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java new file mode 100644 index 000000000..b5dccbe9c --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java @@ -0,0 +1,19 @@ +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"; + } + + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java new file mode 100644 index 000000000..8c637b8cb --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java @@ -0,0 +1,46 @@ +package org.argeo.api.cms; + +import java.io.InputStream; + +/** Read and write access to images. */ +public interface CmsImageManager { + /** 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); + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java new file mode 100644 index 000000000..206cfd649 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java @@ -0,0 +1,182 @@ +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; + } + + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java new file mode 100644 index 000000000..18d53cee8 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java @@ -0,0 +1,40 @@ +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); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java new file mode 100644 index 000000000..0e4778953 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java @@ -0,0 +1,39 @@ +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; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java new file mode 100644 index 000000000..ed8698fca --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java @@ -0,0 +1,9 @@ +package org.argeo.api.cms; + +/** A running node process. */ +public interface CmsState { + String getHostname(); + + Long getAvailableSince(); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java new file mode 100644 index 000000000..8444e2fc5 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java @@ -0,0 +1,22 @@ +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 ""; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java new file mode 100644 index 000000000..50c3b1f25 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java @@ -0,0 +1,45 @@ +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 + * null if not found + */ + InputStream getResourceAsStream(String resourceName) throws IOException; + + /** Relative paths to standard web CSS. */ + Set getWebCssPaths(); + + /** Relative paths to RAP specific CSS. */ + Set getRapCssPaths(); + + /** Relative paths to SWT specific CSS. */ + Set getSwtCssPaths(); + + /** Relative paths to images such as icons. */ + Set getImagesPaths(); + + /** Relative paths to fonts. */ + Set 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; + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java new file mode 100644 index 000000000..fd91c6e34 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java @@ -0,0 +1,7 @@ +package org.argeo.api.cms; + +public interface CmsUi { + Object getData(String key); + void setData(String key, Object value); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java new file mode 100644 index 000000000..c7ca1e90c --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java @@ -0,0 +1,97 @@ +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 properties) { + + } + + /** + * Convenience methods for when {@link #sendEvent(String, Map)} only requires + * one single parameter. + */ + default void sendEvent(String topic, String param, Object value) { + Map properties = new HashMap<>(); + properties.put(param, value); + sendEvent(topic, properties); + } + + default void applyStyles(Object widget) { + + } + + default T doAs(PrivilegedAction action) { + throw new UnsupportedOperationException(); + } + + default Void runAs(Runnable runnable) { + return doAs(new PrivilegedAction() { + + @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 getUiContext(Class clss) { + return (T) getData(clss.getName()); + } + + default void setUiContext(Class clss, T instance) { + setData(clss.getName(), instance); + } + + default void setData(String key, Object value) { + throw new UnsupportedOperationException(); + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java b/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java new file mode 100644 index 000000000..bc12bcbe2 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java @@ -0,0 +1,29 @@ +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(); + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java new file mode 100644 index 000000000..c1aa6006c --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java @@ -0,0 +1,44 @@ +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 extends BiFunction { + 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); + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java new file mode 100644 index 000000000..fb99178ee --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java @@ -0,0 +1,18 @@ +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(); +} diff --git a/org.argeo.api/.classpath b/org.argeo.api/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/org.argeo.api/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.api/.project b/org.argeo.api/.project deleted file mode 100644 index a396aadc8..000000000 --- a/org.argeo.api/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.api - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.api/META-INF/.gitignore b/org.argeo.api/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/org.argeo.api/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/org.argeo.api/bnd.bnd b/org.argeo.api/bnd.bnd deleted file mode 100644 index a8a1e4f6a..000000000 --- a/org.argeo.api/bnd.bnd +++ /dev/null @@ -1,4 +0,0 @@ -Import-Package: javax.naming.*,\ -javax.security.* - -Export-Package: org.argeo.api.* \ No newline at end of file diff --git a/org.argeo.api/build.properties b/org.argeo.api/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/org.argeo.api/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/org.argeo.api/pom.xml b/org.argeo.api/pom.xml deleted file mode 100644 index 82ad85552..000000000 --- a/org.argeo.api/pom.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - 4.0.0 - - org.argeo.commons - argeo-commons - 2.3-SNAPSHOT - .. - - org.argeo.api - CMS APIs - jar - - - \ No newline at end of file diff --git a/org.argeo.api/src/org/argeo/api/cms/AnonymousPrincipal.java b/org.argeo.api/src/org/argeo/api/cms/AnonymousPrincipal.java deleted file mode 100644 index 63ee34853..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/AnonymousPrincipal.java +++ /dev/null @@ -1,28 +0,0 @@ -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(); - } -} diff --git a/org.argeo.api/src/org/argeo/api/cms/Cms2DSize.java b/org.argeo.api/src/org/argeo/api/cms/Cms2DSize.java deleted file mode 100644 index 30b3d8100..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/Cms2DSize.java +++ /dev/null @@ -1,34 +0,0 @@ -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; - } - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsApp.java b/org.argeo.api/src/org/argeo/api/cms/CmsApp.java deleted file mode 100644 index 761191e5d..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsApp.java +++ /dev/null @@ -1,31 +0,0 @@ -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 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); -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsAppListener.java b/org.argeo.api/src/org/argeo/api/cms/CmsAppListener.java deleted file mode 100644 index 55fcec56b..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsAppListener.java +++ /dev/null @@ -1,7 +0,0 @@ -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(); -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsAuth.java b/org.argeo.api/src/org/argeo/api/cms/CmsAuth.java deleted file mode 100644 index decea3550..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsAuth.java +++ /dev/null @@ -1,46 +0,0 @@ -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(); - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsConstants.java b/org.argeo.api/src/org/argeo/api/cms/CmsConstants.java deleted file mode 100644 index 8fe9846f4..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsConstants.java +++ /dev/null @@ -1,126 +0,0 @@ -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"; -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsContext.java b/org.argeo.api/src/org/argeo/api/cms/CmsContext.java deleted file mode 100644 index fa26b253a..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsContext.java +++ /dev/null @@ -1,26 +0,0 @@ -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 getLocales(); - - Long getAvailableSince(); - - - /** Mark this group as a workgroup */ - void createWorkgroup(String groupDn); -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java b/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java deleted file mode 100644 index 5893d2ec5..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.api.cms; - -import java.util.Dictionary; - -/** A configured node deployment. */ -public interface CmsDeployment { - - void addFactoryDeployConfig(String factoryPid, Dictionary props); - - Dictionary getProps(String factoryPid, String cn); -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsEditable.java b/org.argeo.api/src/org/argeo/api/cms/CmsEditable.java deleted file mode 100644 index 2deca018e..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsEditable.java +++ /dev/null @@ -1,57 +0,0 @@ -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; - } - }; - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsEvent.java b/org.argeo.api/src/org/argeo/api/cms/CmsEvent.java deleted file mode 100644 index b5dccbe9c..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -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"; - } - - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsImageManager.java b/org.argeo.api/src/org/argeo/api/cms/CmsImageManager.java deleted file mode 100644 index 8c637b8cb..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsImageManager.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.api.cms; - -import java.io.InputStream; - -/** Read and write access to images. */ -public interface CmsImageManager { - /** 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); - } -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsLog.java b/org.argeo.api/src/org/argeo/api/cms/CmsLog.java deleted file mode 100644 index 206cfd649..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsLog.java +++ /dev/null @@ -1,182 +0,0 @@ -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; - } - - } - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsSession.java b/org.argeo.api/src/org/argeo/api/cms/CmsSession.java deleted file mode 100644 index 18d53cee8..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsSession.java +++ /dev/null @@ -1,40 +0,0 @@ -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); -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsSessionId.java b/org.argeo.api/src/org/argeo/api/cms/CmsSessionId.java deleted file mode 100644 index 0e4778953..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsSessionId.java +++ /dev/null @@ -1,39 +0,0 @@ -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; - } - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsState.java b/org.argeo.api/src/org/argeo/api/cms/CmsState.java deleted file mode 100644 index ed8698fca..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsState.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.api.cms; - -/** A running node process. */ -public interface CmsState { - String getHostname(); - - Long getAvailableSince(); - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsStyle.java b/org.argeo.api/src/org/argeo/api/cms/CmsStyle.java deleted file mode 100644 index 8444e2fc5..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsStyle.java +++ /dev/null @@ -1,22 +0,0 @@ -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 ""; - } - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsTheme.java b/org.argeo.api/src/org/argeo/api/cms/CmsTheme.java deleted file mode 100644 index 50c3b1f25..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsTheme.java +++ /dev/null @@ -1,45 +0,0 @@ -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 - * null if not found - */ - InputStream getResourceAsStream(String resourceName) throws IOException; - - /** Relative paths to standard web CSS. */ - Set getWebCssPaths(); - - /** Relative paths to RAP specific CSS. */ - Set getRapCssPaths(); - - /** Relative paths to SWT specific CSS. */ - Set getSwtCssPaths(); - - /** Relative paths to images such as icons. */ - Set getImagesPaths(); - - /** Relative paths to fonts. */ - Set 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; - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsUi.java b/org.argeo.api/src/org/argeo/api/cms/CmsUi.java deleted file mode 100644 index fd91c6e34..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsUi.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.api.cms; - -public interface CmsUi { - Object getData(String key); - void setData(String key, Object value); - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/CmsView.java b/org.argeo.api/src/org/argeo/api/cms/CmsView.java deleted file mode 100644 index c7ca1e90c..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/CmsView.java +++ /dev/null @@ -1,97 +0,0 @@ -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 properties) { - - } - - /** - * Convenience methods for when {@link #sendEvent(String, Map)} only requires - * one single parameter. - */ - default void sendEvent(String topic, String param, Object value) { - Map properties = new HashMap<>(); - properties.put(param, value); - sendEvent(topic, properties); - } - - default void applyStyles(Object widget) { - - } - - default T doAs(PrivilegedAction action) { - throw new UnsupportedOperationException(); - } - - default Void runAs(Runnable runnable) { - return doAs(new PrivilegedAction() { - - @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 getUiContext(Class clss) { - return (T) getData(clss.getName()); - } - - default void setUiContext(Class clss, T instance) { - setData(clss.getName(), instance); - } - - default void setData(String key, Object value) { - throw new UnsupportedOperationException(); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/DataAdminPrincipal.java b/org.argeo.api/src/org/argeo/api/cms/DataAdminPrincipal.java deleted file mode 100644 index bc12bcbe2..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/DataAdminPrincipal.java +++ /dev/null @@ -1,29 +0,0 @@ -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(); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/cms/MvcProvider.java b/org.argeo.api/src/org/argeo/api/cms/MvcProvider.java deleted file mode 100644 index c1aa6006c..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/MvcProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -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 extends BiFunction { - 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); - } -} diff --git a/org.argeo.api/src/org/argeo/api/cms/UxContext.java b/org.argeo.api/src/org/argeo/api/cms/UxContext.java deleted file mode 100644 index fb99178ee..000000000 --- a/org.argeo.api/src/org/argeo/api/cms/UxContext.java +++ /dev/null @@ -1,18 +0,0 @@ -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(); -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java b/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java deleted file mode 100644 index a628cda78..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.api.gcr; - -/** - * An attribute type MUST consistently parse a string to an object so that - * parse(obj.toString()).equals(obj) is verified. - * {@link #format(Object)} can be overridden to provide more efficient - * implementations but the returned - * String MUST be the same, that is format(obj).equals(obj.toString()) - * is verified. - */ -public interface AttributeFormatter { - /** 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(); - } -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/CompositeString.java b/org.argeo.api/src/org/argeo/api/gcr/CompositeString.java deleted file mode 100644 index f979357b1..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/CompositeString.java +++ /dev/null @@ -1,164 +0,0 @@ -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 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()); - } -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/Content.java b/org.argeo.api/src/org/argeo/api/gcr/Content.java deleted file mode 100644 index fa4d50713..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/Content.java +++ /dev/null @@ -1,118 +0,0 @@ -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, Map { - - QName getName(); - - String getPath(); - - Content getParent(); - - /* - * ATTRIBUTES OPERATIONS - */ - - Optional get(QName key, Class 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); - - Optional> getMultiple(QName key, Class clss); - - default List getMultiple(QName key) { - Class type; - try { - type = (Class) getType(key); - } catch (ClassCastException e) { - throw new IllegalArgumentException("Requested type is not the default type"); - } - Optional> 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 adapt(Class clss) throws IllegalArgumentException { - throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName()); - } - - default C open(Class 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 get(Object key, Class clss) { -// return key != null ? get(key.toString(), clss) : get(null, clss); -// } - - /* - * EXPERIMENTAL UNSUPPORTED - */ - default boolean hasText() { - return false; - } - - default String getText() { - throw new UnsupportedOperationException(); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentFeatureUnsupportedException.java b/org.argeo.api/src/org/argeo/api/gcr/ContentFeatureUnsupportedException.java deleted file mode 100644 index 0e97a2427..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentFeatureUnsupportedException.java +++ /dev/null @@ -1,22 +0,0 @@ -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); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentName.java b/org.argeo.api/src/org/argeo/api/gcr/ContentName.java deleted file mode 100644 index b23042528..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentName.java +++ /dev/null @@ -1,165 +0,0 @@ -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 namespaceUuids = Collections.synchronizedMap(new TreeMap<>()); - private final static Map 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; - } -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java b/org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java deleted file mode 100644 index 0c1020148..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java +++ /dev/null @@ -1,106 +0,0 @@ -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, 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 getPrefixes(String namespaceURI) { - Iterator 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 getStandardPrefixes(String namespaceURI) { - String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI); - if (prefix == null) - return null; - return Collections.singleton(prefix).iterator(); - } - - static String getStandardNamespaceURI(String prefix) { - if (prefix == null) - throw new IllegalArgumentException("Prefix cannot be null"); - if (XMLConstants.XML_NS_PREFIX.equals(prefix)) - return XMLConstants.XML_NS_URI; - else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) - return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; - return null; - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentNotFoundException.java b/org.argeo.api/src/org/argeo/api/gcr/ContentNotFoundException.java deleted file mode 100644 index 8a0909342..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentNotFoundException.java +++ /dev/null @@ -1,16 +0,0 @@ -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); - } - - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java b/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java deleted file mode 100644 index 0807075ce..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -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 get(Locale locale); -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentResourceException.java b/org.argeo.api/src/org/argeo/api/gcr/ContentResourceException.java deleted file mode 100644 index fa7195e7f..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentResourceException.java +++ /dev/null @@ -1,25 +0,0 @@ -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); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java deleted file mode 100644 index 6c80189b2..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java +++ /dev/null @@ -1,49 +0,0 @@ -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(); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java b/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java deleted file mode 100644 index c9c90bc38..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.argeo.api.gcr; - -public interface ContentStore { - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java b/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java deleted file mode 100644 index cf5115590..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -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 doIt) { - traverse(content, doIt, 0); - } - - public static void traverse(Content content, BiConsumer 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(""); - } - } - } - - 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 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 boolean isString(T t) { - return t instanceof String; - } - - /** Singleton. */ - private ContentUtils() { - - } -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java b/org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java deleted file mode 100644 index 1e3445653..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.argeo.api.gcr; - -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Instant; -import java.time.format.DateTimeParseException; -import java.util.UUID; - -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 - * String. - */ -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 CrAttributeType(Class clss, AttributeFormatter 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 { - - /** - * @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 { - @Override - public Integer parse(String str) throws NumberFormatException { - return Integer.parseInt(str); - } - } - - static class LongFormatter implements AttributeFormatter { - @Override - public Long parse(String str) throws NumberFormatException { - return Long.parseLong(str); - } - } - - static class DoubleFormatter implements AttributeFormatter { - - @Override - public Double parse(String str) throws NumberFormatException { - return Double.parseDouble(str); - } - } - - static class InstantFormatter implements AttributeFormatter { - - @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 { - - @Override - public UUID parse(String str) throws IllegalArgumentException { - return java.util.UUID.fromString(str); - } - } - - static class UriFormatter implements AttributeFormatter { - - @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 { - - @Override - public String parse(String str) { - return str; - } - - @Override - public String format(String obj) { - return obj; - } - - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/CrName.java b/org.argeo.api/src/org/argeo/api/gcr/CrName.java deleted file mode 100644 index 283e5cc71..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/CrName.java +++ /dev/null @@ -1,58 +0,0 @@ -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; - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java deleted file mode 100644 index c83fe385b..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java +++ /dev/null @@ -1,387 +0,0 @@ -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, 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 iterator() { - return new Iterator() { - 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 newInstance(String path); - - protected abstract AbstractFsPath 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 getRoot() { - return newInstance(toStringRoot()); - } - - public AbstractFsPath 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 getFileName() { - if (isRoot()) - return null; - return newInstance(segments[segments.length - 1]); - } - - public int getNameCount() { - if (isRoot()) - return 0; - return segments.length; - } - - public AbstractFsPath getName(int index) { - if (isRoot()) - return null; - return newInstance(segments[index]); - } - - public AbstractFsPath 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(); - } - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java deleted file mode 100644 index 00887346d..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.api.gcr.fs; - -import java.nio.file.FileStore; - -public abstract class AbstractFsStore extends FileStore { - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java deleted file mode 100644 index 36369ca6e..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.api.gcr.fs; - -import java.nio.file.FileSystem; - -public abstract class AbstractFsSystem extends FileSystem { - public abstract ST getBaseFileStore(); - - public abstract ST getFileStore(String path); -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java b/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java deleted file mode 100644 index 2d3bcde93..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java +++ /dev/null @@ -1,164 +0,0 @@ -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 implements Content { - - /* - * ATTRIBUTES OPERATIONS - */ - protected abstract Iterable keys(); - - protected abstract void removeAttr(QName key); - - @Override - public Set> entrySet() { - Set> result = new AttrSet(); - return result; - } - - @Override - public Class getType(QName key) { - return String.class; - } - - @Override - public boolean isMultiple(QName key) { - return false; - } - - @Override - public Optional> getMultiple(QName key, Class clss) { - Object value = get(key); - if (value == null) - return null; - if (value instanceof List) { - try { - List res = (List) value; - return Optional.of(res); - } catch (ClassCastException e) { - List 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 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 ancestors, Content content) { - if (content == null) - return; - ancestors.add(0, content); - collectAncestors(ancestors, content.getParent()); - } - - /* - * UTILITIES - */ - protected boolean isDefaultAttrTypeRequested(Class clss) { - // check whether clss is Object.class - return clss.isAssignableFrom(Object.class); - } - - @Override - public String toString() { - return "content " + getPath(); - } - - /* - * SUB CLASSES - */ - - class AttrSet extends AbstractSet> { - - @Override - public Iterator> iterator() { - final Iterator keys = keys().iterator(); - Iterator> it = new Iterator>() { - - QName key = null; - - @Override - public boolean hasNext() { - return keys.hasNext(); - } - - @Override - public Entry next() { - key = keys.next(); - // TODO check type - Optional value = get(key, Object.class); - assert !value.isEmpty(); - AbstractMap.SimpleEntry 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; - } - - } -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java deleted file mode 100644 index 3a24ca24b..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.api.gcr.spi; - -import org.argeo.api.gcr.Content; - -public interface ContentProvider { - - Content get(ProvidedSession session, String mountPath, String relativePath); - -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java deleted file mode 100644 index e75f62d45..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.api.gcr.spi; - -import org.argeo.api.gcr.Content; - -public interface ProvidedContent extends Content { - ProvidedSession getSession(); - - ContentProvider getProvider(); -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java deleted file mode 100644 index 2db13f9a1..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.argeo.api.gcr.spi; - -import org.argeo.api.gcr.ContentRepository; - -public interface ProvidedRepository extends ContentRepository { -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.java b/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.java deleted file mode 100644 index 4e93684e5..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.java +++ /dev/null @@ -1,68 +0,0 @@ -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 findPrefixes(String namespaceURI); - - /** To be overridden for optimisation, as it will be called a lot */ - default String findPrefix(String namespaceURI) { - Set 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 getPrefixes(String namespaceURI) { - Iterator 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 prefixes = findPrefixes(namespaceURI); - assert prefixes != null; - return prefixes.iterator(); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java new file mode 100644 index 000000000..04c5d2d8c --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -0,0 +1,201 @@ +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 Optional get(QName key, Class clss) { + if (isDefaultAttrTypeRequested(clss)) { + return Optional.of((A) get(jcrNode, key.toString())); + } + return Optional.of((A) Jcr.get(jcrNode, key.toString())); + } + + @Override + public Iterator iterator() { + try { + return new JcrContentIterator(jcrNode.getNodes()); + } catch (RepositoryException e) { + throw new JcrException("Cannot list children of " + jcrNode, e); + } + } + + @Override + protected Iterable keys() { + return new Iterable() { + + @Override + public Iterator 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 { + 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 { + 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); + } + } + + } +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java new file mode 100644 index 000000000..ef8e375d0 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java @@ -0,0 +1,70 @@ +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 getPrefixes(String namespaceURI) { + try { + return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator(); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java deleted file mode 100644 index af63ead61..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java +++ /dev/null @@ -1,201 +0,0 @@ -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 Optional get(QName key, Class clss) { - if (isDefaultAttrTypeRequested(clss)) { - return Optional.of((A) get(jcrNode, key.toString())); - } - return Optional.of((A) Jcr.get(jcrNode, key.toString())); - } - - @Override - public Iterator iterator() { - try { - return new JcrContentIterator(jcrNode.getNodes()); - } catch (RepositoryException e) { - throw new JcrException("Cannot list children of " + jcrNode, e); - } - } - - @Override - protected Iterable keys() { - return new Iterable() { - - @Override - public Iterator 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 { - 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 { - 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); - } - } - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java deleted file mode 100644 index 761c087f0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -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 getPrefixes(String namespaceURI) { - try { - return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java index 3d0fcfe66..4b329810f 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java @@ -23,8 +23,8 @@ import javax.jcr.RepositoryException; 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; diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java index 8782be9ee..7318b7096 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java @@ -5,7 +5,7 @@ import java.nio.file.Path; 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 { diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java index e8f24c9de..ce4205a97 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java @@ -11,7 +11,7 @@ import javax.jcr.RepositoryException; 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}. */ diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java index ba6d7b477..4039f2baa 100644 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java +++ b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java @@ -5,8 +5,8 @@ import java.nio.file.Paths; import javax.xml.namespace.QName; -import org.argeo.api.gcr.Content; -import org.argeo.cms.gcr.fs.FsContentProvider; +import org.argeo.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; diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java index 63b29c963..8109c40ac 100644 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java +++ b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java @@ -1,7 +1,7 @@ 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; diff --git a/org.argeo.cms.tp/pom.xml b/org.argeo.cms.tp/pom.xml index 2bc5b03d1..7a394495a 100644 --- a/org.argeo.cms.tp/pom.xml +++ b/org.argeo.cms.tp/pom.xml @@ -12,11 +12,11 @@ org.argeo.cms.tp jar CMS Third Parties Adapters - Workarounds or trivial implementations from some third parties, typically logging + Workarounds or trivial implementations of some third parties, typically logging org.argeo.commons - org.argeo.api + org.argeo.api.cms 2.3-SNAPSHOT diff --git a/org.argeo.cms/pom.xml b/org.argeo.cms/pom.xml index 237af520b..38112fc3b 100644 --- a/org.argeo.cms/pom.xml +++ b/org.argeo.cms/pom.xml @@ -13,7 +13,12 @@ org.argeo.commons - org.argeo.api + org.argeo.api.cms + 2.3-SNAPSHOT + + + org.argeo.commons + org.argeo.api.acr 2.3-SNAPSHOT @@ -21,15 +26,5 @@ org.argeo.util 2.3-SNAPSHOT - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java new file mode 100644 index 000000000..9cd8bd22d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -0,0 +1,139 @@ +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 partitions = new TreeMap<>(); + + // TODO synchronize ? + private NavigableMap 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 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 findPrefixes(String namespaceURI) { + Set 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); + } + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java new file mode 100644 index 000000000..bfcd0118d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java @@ -0,0 +1,219 @@ +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 BASIC_KEYS; + private static final Map 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 Optional get(QName key, Class 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 keys() { + Set 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 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; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java new file mode 100644 index 000000000..99ed3a8ca --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java @@ -0,0 +1,32 @@ +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)); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java new file mode 100644 index 000000000..626f582e5 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -0,0 +1,221 @@ +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 keys() { + // TODO implement an iterator? + Set 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 Optional get(QName key, Class 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 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; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java new file mode 100644 index 000000000..c5fde8d7c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java @@ -0,0 +1,99 @@ +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; + + 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 getPrefixes(String namespaceURI) { + List res = new ArrayList<>(); + res.add(getPrefix(namespaceURI)); + return Collections.unmodifiableList(res).iterator(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java new file mode 100644 index 000000000..3b07081e4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java @@ -0,0 +1,57 @@ +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 { + 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; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java deleted file mode 100644 index 69f2e94d4..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java +++ /dev/null @@ -1,139 +0,0 @@ -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 partitions = new TreeMap<>(); - - // TODO synchronize ? - private NavigableMap 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 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 findPrefixes(String namespaceURI) { - Set 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); - } - - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java deleted file mode 100644 index 0ba24093f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java +++ /dev/null @@ -1,219 +0,0 @@ -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 BASIC_KEYS; - private static final Map 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 Optional get(QName key, Class 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 keys() { - Set 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 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; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java deleted file mode 100644 index e5732da1e..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -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)); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java deleted file mode 100644 index 0b68a772c..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java +++ /dev/null @@ -1,221 +0,0 @@ -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 keys() { - // TODO implement an iterator? - Set 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 Optional get(QName key, Class 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 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; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java deleted file mode 100644 index dc200bf3d..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java +++ /dev/null @@ -1,99 +0,0 @@ -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; - - 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 getPrefixes(String namespaceURI) { - List res = new ArrayList<>(); - res.add(getPrefix(namespaceURI)); - return Collections.unmodifiableList(res).iterator(); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java deleted file mode 100644 index db25b653d..000000000 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java +++ /dev/null @@ -1,57 +0,0 @@ -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 { - 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; - } - -} diff --git a/pom.xml b/pom.xml index 41f257e50..c0edf6c4d 100644 --- a/pom.xml +++ b/pom.xml @@ -22,9 +22,10 @@ org.argeo.init org.argeo.util - + + org.argeo.api.acr + org.argeo.api.cms - org.argeo.api org.argeo.cms.tp org.argeo.cms org.argeo.cms.pgsql