Rename GCR to ACR.
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 22 Jan 2022 05:14:42 +0000 (06:14 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 22 Jan 2022 05:14:42 +0000 (06:14 +0100)
132 files changed:
dep/org.argeo.dep.cms.base/pom.xml
org.argeo.api.acr/.classpath [new file with mode: 0644]
org.argeo.api.acr/.project [new file with mode: 0644]
org.argeo.api.acr/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.argeo.api.acr/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
org.argeo.api.acr/bnd.bnd [new file with mode: 0644]
org.argeo.api.acr/build.properties [new file with mode: 0644]
org.argeo.api.acr/pom.xml [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/Content.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/CrName.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java [new file with mode: 0644]
org.argeo.api.cms/.classpath [new file with mode: 0644]
org.argeo.api.cms/.project [new file with mode: 0644]
org.argeo.api.cms/META-INF/.gitignore [new file with mode: 0644]
org.argeo.api.cms/bnd.bnd [new file with mode: 0644]
org.argeo.api.cms/build.properties [new file with mode: 0644]
org.argeo.api.cms/pom.xml [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java [new file with mode: 0644]
org.argeo.api/.classpath [deleted file]
org.argeo.api/.project [deleted file]
org.argeo.api/META-INF/.gitignore [deleted file]
org.argeo.api/bnd.bnd [deleted file]
org.argeo.api/build.properties [deleted file]
org.argeo.api/pom.xml [deleted file]
org.argeo.api/src/org/argeo/api/cms/AnonymousPrincipal.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/Cms2DSize.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsApp.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsAppListener.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsAuth.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsConstants.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsContext.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsDeployment.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsEditable.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsEvent.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsImageManager.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsLog.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsSession.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsSessionId.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsState.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsStyle.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsTheme.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsUi.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/CmsView.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/DataAdminPrincipal.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/MvcProvider.java [deleted file]
org.argeo.api/src/org/argeo/api/cms/UxContext.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/CompositeString.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/Content.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentFeatureUnsupportedException.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentName.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentNameSupplier.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentNotFoundException.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentResourceException.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentSession.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentStore.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/CrAttributeType.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/CrName.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/spi/ContentProvider.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedContent.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedRepository.java [deleted file]
org.argeo.api/src/org/argeo/api/gcr/spi/ProvidedSession.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java [new file with mode: 0644]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java
org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java
org.argeo.cms.tp/pom.xml
org.argeo.cms/pom.xml
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/gcr/CmsContentRepository.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentProvider.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentProvider.java [deleted file]
org.argeo.cms/src/org/argeo/cms/gcr/xml/ElementIterator.java [deleted file]
pom.xml

index a46e8ec3066cc792f0ef72bb8827b04c14075111..3a2af17f17abb6f69df6deed50a5193d78bbd108 100644 (file)
                        <artifactId>org.argeo.util</artifactId>
                        <version>2.3-SNAPSHOT</version>
                </dependency>
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.commons</groupId> -->
-               <!-- <artifactId>org.argeo.jcr</artifactId> -->
-               <!-- <version>2.3-SNAPSHOT</version> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.commons</groupId> -->
-               <!-- <artifactId>org.argeo.core</artifactId> -->
-               <!-- <version>2.3-SNAPSHOT</version> -->
-               <!-- </dependency> -->
 
                <dependency>
                        <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.api</artifactId>
+                       <artifactId>org.argeo.api.acr</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.api.cms</artifactId>
                        <version>2.3-SNAPSHOT</version>
                </dependency>
                <dependency>
diff --git a/org.argeo.api.acr/.classpath b/org.argeo.api.acr/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.api.acr/.project b/org.argeo.api.acr/.project
new file mode 100644 (file)
index 0000000..d7a785e
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.acr</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
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 (file)
index 0000000..c9545f0
--- /dev/null
@@ -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 (file)
index 0000000..f29e940
--- /dev/null
@@ -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 (file)
index 0000000..6e839c0
--- /dev/null
@@ -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 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -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 (file)
index 0000000..bc23838
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>argeo-commons</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.api.acr</artifactId>
+       <name>ACR API</name>
+       <packaging>jar</packaging>
+</project>
\ No newline at end of file
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 (file)
index 0000000..4160033
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.api.acr;
+
+/**
+ * An attribute type MUST consistently parse a string to an object so that
+ * <code>parse(obj.toString()).equals(obj)</code> is verified.
+ * {@link #format(Object)} can be overridden to provide more efficient
+ * implementations but the returned
+ * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
+ * is verified.
+ */
+public interface AttributeFormatter<T> {
+       /** Parses a String to a Java object. */
+       T parse(String str) throws IllegalArgumentException;
+
+       /** Default implementation returns {@link Object#toString()} on the argument. */
+       default String format(T obj) {
+               return obj.toString();
+       }
+}
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 (file)
index 0000000..b83fe30
--- /dev/null
@@ -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<String> res = new ArrayList<>();
+               if (separator != null) {
+                       StringTokenizer st = new StringTokenizer(str, separator.toString());
+                       while (st.hasMoreTokens()) {
+                               res.add(st.nextToken().toLowerCase());
+                       }
+               } else {
+                       // single
+                       String strLowerCase = str.toLowerCase();
+                       if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
+                               return new String[] { strLowerCase };
+
+                       // CAML
+                       StringBuilder current = null;
+                       for (char c : str.toCharArray()) {
+                               if (Character.isUpperCase(c)) {
+                                       if (current != null)
+                                               res.add(current.toString());
+                                       current = new StringBuilder();
+                               }
+                               if (current == null)// first char is lower case
+                                       current = new StringBuilder();
+                               current.append(Character.toLowerCase(c));
+                       }
+                       res.add(current.toString());
+               }
+               return res.toArray(new String[res.size()]);
+       }
+
+       private static void checkNo(String str, Character c) {
+               if (str.indexOf(c) >= 0) {
+                       throw new IllegalArgumentException("Only one kind of sperator is allowed");
+               }
+       }
+
+       private static int hashCode(String[] parts) {
+               int hashCode = 0;
+               for (String part : parts) {
+                       hashCode = hashCode + part.hashCode();
+               }
+               return hashCode;
+       }
+
+       static boolean smokeTests() {
+               CompositeString plainName = new CompositeString("NAME");
+               assert "name".equals(plainName.toString());
+               assert "NAME".equals(plainName.toString(UNDERSCORE, true));
+               assert "name".equals(plainName.toString(UNDERSCORE, false));
+               assert "name".equals(plainName.toStringCaml(false));
+               assert "Name".equals(plainName.toStringCaml(true));
+
+               CompositeString camlName = new CompositeString("myComplexName");
+
+               assert new CompositeString("my-complex-name").equals(camlName);
+               assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
+               assert new CompositeString("My complex Name").equals(camlName);
+               assert new CompositeString("MyComplexName").equals(camlName);
+
+               assert "my-complex-name".equals(camlName.toString());
+               assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
+               assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
+               assert "myComplexName".equals(camlName.toStringCaml(false));
+               assert "MyComplexName".equals(camlName.toStringCaml(true));
+
+               return CompositeString.class.desiredAssertionStatus();
+       }
+
+       public static void main(String[] args) {
+               System.out.println(smokeTests());
+       }
+}
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 (file)
index 0000000..edcfaea
--- /dev/null
@@ -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<Content>, Map<QName, Object> {
+
+       QName getName();
+
+       String getPath();
+
+       Content getParent();
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+
+       <A> Optional<A> get(QName key, Class<A> clss);
+
+       default Object get(String key) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       }
+
+       default Object put(String key, Object value) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
+       }
+
+       default Object remove(String key) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       }
+
+       Class<?> getType(QName key);
+
+       boolean isMultiple(QName key);
+
+       <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
+
+       default <A> List<A> getMultiple(QName key) {
+               Class<A> type;
+               try {
+                       type = (Class<A>) getType(key);
+               } catch (ClassCastException e) {
+                       throw new IllegalArgumentException("Requested type is not the default type");
+               }
+               Optional<List<A>> res = getMultiple(key, type);
+               if (res == null)
+                       return null;
+               else {
+                       if (res.isEmpty())
+                               throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
+                       return res.get();
+               }
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+       Content add(QName name, QName... classes);
+
+       default Content add(String name, QName... classes) {
+               if (name.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + name + " has a prefix");
+               return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
+       }
+
+       void remove();
+
+       /*
+        * DEFAULT METHODS
+        */
+       default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+               throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
+       }
+
+       default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
+               throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
+       }
+
+       /*
+        * CONVENIENCE METHODS
+        */
+//     default String attr(String key) {
+//             return get(key, String.class);
+//     }
+//
+//     default String attr(Object key) {
+//             return key != null ? attr(key.toString()) : attr(null);
+//     }
+//
+//     default <A> A get(Object key, Class<A> clss) {
+//             return key != null ? get(key.toString(), clss) : get(null, clss);
+//     }
+
+       /*
+        * EXPERIMENTAL UNSUPPORTED
+        */
+       default boolean hasText() {
+               return false;
+       }
+
+       default String getText() {
+               throw new UnsupportedOperationException();
+       }
+
+}
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 (file)
index 0000000..485caa9
--- /dev/null
@@ -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 (file)
index 0000000..341a3e2
--- /dev/null
@@ -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<String, UUID> namespaceUuids = Collections.synchronizedMap(new TreeMap<>());
+       private final static Map<String, UUID> nameUuids = Collections.synchronizedMap(new TreeMap<>());
+
+       static {
+               assert NULL_NS_UUID.equals(nameUUIDv3(XMLNS_UUID, XMLConstants.NULL_NS_URI.getBytes(UTF_8)));
+       }
+
+//     private final UUID uuid;
+
+       public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
+               super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
+       }
+
+       private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
+               Objects.requireNonNull(nsContext, "Namespace context cannot be null");
+               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+               String prefix = nsContext.getNamespaceURI(namespaceURI);
+               if (prefix == null)
+                       throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
+               return prefix;
+       }
+
+       ContentName(String namespaceURI, String localPart, String prefix) {
+               super(namespaceURI, localPart, prefix);
+       }
+
+       public ContentName(String localPart) {
+               super(XMLConstants.NULL_NS_URI, localPart, XMLConstants.DEFAULT_NS_PREFIX);
+       }
+
+       public ContentName(QName qName, NamespaceContext nsContext) {
+               this(qName.getNamespaceURI(), qName.getLocalPart(), nsContext);
+       }
+
+       public String toQNameString() {
+               return super.toString();
+       }
+
+       public String toPrefixedString() {
+               return toPrefixedString(this);
+       }
+
+       /*
+        * OBJECT METHOS
+        */
+
+       @Override
+       public String toString() {
+               return toPrefixedString();
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new ContentName(getNamespaceURI(), getLocalPart(), getPrefix());
+       }
+
+       public static String toPrefixedString(QName name) {
+               String prefix = name.getPrefix();
+               assert prefix != null;
+               return "".equals(prefix) ? name.getLocalPart() : prefix + ":" + name.getLocalPart();
+       }
+//     ContentNamespace getNamespace();
+//
+//     String getName();
+
+       public static UUID namespaceUuid(String namespaceURI) {
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return NULL_NS_UUID;
+               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+               synchronized (namespaceUuids) {
+                       UUID namespaceUuid = namespaceUuids.get(namespaceURI);
+                       if (namespaceUuid == null) {
+                               namespaceUuid = nameUUIDv3(ContentName.XMLNS_UUID,
+                                               namespaceURI.toString().getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(namespaceURI, namespaceUuid);
+                       }
+                       return namespaceUuid;
+               }
+       }
+
+       public static UUID nameUuid(String namespaceURI, QName name) {
+               return nameUuid(name.getNamespaceURI(), name.getLocalPart());
+       }
+
+       public static UUID nameUuid(String namespaceURI, String name) {
+               Objects.requireNonNull(namespaceURI, "Namespace cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
+               synchronized (nameUuids) {
+                       String key = XMLConstants.NULL_NS_URI.equals(namespaceURI) ? name : "{" + namespaceURI + "}" + name;
+                       UUID nameUuid = nameUuids.get(key);
+                       if (nameUuid == null) {
+                               UUID namespaceUuid = namespaceUuid(namespaceURI);
+                               nameUuid = nameUUIDv3(namespaceUuid, name.getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(key, nameUuid);
+                       }
+                       return nameUuid;
+               }
+       }
+
+       /*
+        * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+        */
+       static UUID nameUUIDv3(UUID namespace, byte[] name) {
+               byte[] arr = new byte[name.length + 16];
+               ContentName.copyUuidBytes(namespace, arr, 0);
+               System.arraycopy(name, 0, arr, 16, name.length);
+               return UUID.nameUUIDFromBytes(arr);
+       }
+
+       static void copyUuidBytes(UUID uuid, byte[] arr, int offset) {
+               long msb = uuid.getMostSignificantBits();
+               long lsb = uuid.getLeastSignificantBits();
+               assert arr.length >= 16 + offset;
+               for (int i = offset; i < 8 + offset; i++)
+                       arr[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
+               for (int i = 8 + offset; i < 16 + offset; i++)
+                       arr[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
+       }
+
+       /*
+        * UTILITIES
+        */
+
+       public static boolean contains(QName[] classes, QName name) {
+               for (QName clss : classes) {
+                       if (clss.equals(name))
+                               return true;
+               }
+               return false;
+       }
+}
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 (file)
index 0000000..e3c721f
--- /dev/null
@@ -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<ContentName>, NamespaceContext {
+       String name();
+
+       String getNamespaceURI();
+
+       String getDefaultPrefix();
+
+       @Override
+       default ContentName get() {
+               return toContentName();
+       }
+
+       default ContentName toContentName() {
+               CompositeString cs = new CompositeString(name());
+               String camlName = cs.toStringCaml(false);
+               return new ContentName(getNamespaceURI(), camlName, this);
+       }
+
+//     default String getNamespaceURI() {
+//             return XMLConstants.NULL_NS_URI;
+//     }
+//
+//     default String getDefaultPrefix() {
+//             return XMLConstants.DEFAULT_NS_PREFIX;
+//     }
+
+//     static ContentName toContentName(String namespaceURI, String localName, String prefix) {
+//             CompositeString cs = new CompositeString(localName);
+//             String camlName = cs.toStringCaml(false);
+//             return new ContentName(namespaceURI, camlName, this);
+//     }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       @Override
+       default String getNamespaceURI(String prefix) {
+               String namespaceURI = getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (prefix.equals(getDefaultPrefix()))
+                       return getNamespaceURI();
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       @Override
+       default String getPrefix(String namespaceURI) {
+               String prefix = getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (namespaceURI.equals(getNamespaceURI()))
+                       return getDefaultPrefix();
+               return null;
+       }
+
+       @Override
+       default Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> it = getStandardPrefixes(namespaceURI);
+               if (it != null)
+                       return it;
+               if (namespaceURI.equals(getNamespaceURI()))
+                       return Collections.singleton(getDefaultPrefix()).iterator();
+               return Collections.emptyIterator();
+       }
+
+       /*
+        * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
+        */
+       static String getStandardPrefix(String namespaceURI) {
+               if (namespaceURI == null)
+                       throw new IllegalArgumentException("Namespace URI cannot be null");
+               if (XMLConstants.XML_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XML_NS_PREFIX;
+               else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XMLNS_ATTRIBUTE;
+               return null;
+       }
+
+       static Iterator<String> getStandardPrefixes(String namespaceURI) {
+               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
+               if (prefix == null)
+                       return null;
+               return Collections.singleton(prefix).iterator();
+       }
+
+       static String getStandardNamespaceURI(String prefix) {
+               if (prefix == null)
+                       throw new IllegalArgumentException("Prefix cannot be null");
+               if (XMLConstants.XML_NS_PREFIX.equals(prefix))
+                       return XMLConstants.XML_NS_URI;
+               else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
+                       return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+               return null;
+       }
+
+}
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 (file)
index 0000000..b86c92c
--- /dev/null
@@ -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 (file)
index 0000000..3b2f156
--- /dev/null
@@ -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> {
+       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 (file)
index 0000000..e418c4f
--- /dev/null
@@ -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 (file)
index 0000000..215bb9e
--- /dev/null
@@ -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 (file)
index 0000000..cda5d91
--- /dev/null
@@ -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 (file)
index 0000000..2036c86
--- /dev/null
@@ -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<Content, Integer> doIt) {
+               traverse(content, doIt, 0);
+       }
+
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
+               doIt.accept(content, currentDepth);
+               int nextDepth = currentDepth + 1;
+               for (Content child : content) {
+                       traverse(child, doIt, nextDepth);
+               }
+       }
+
+       public static void print(Content content, PrintStream out, int depth, boolean printText) {
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < depth; i++) {
+                       sb.append("  ");
+               }
+               String prefix = sb.toString();
+               out.println(prefix + content.getName());
+               for (QName key : content.keySet()) {
+                       out.println(prefix + " " + key + "=" + content.get(key));
+               }
+               if (printText) {
+                       if (content.hasText()) {
+                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
+                       }
+               }
+       }
+
+       public static URI bytesToDataURI(byte[] arr) {
+               String base64Str = Base64.getEncoder().encodeToString(arr);
+               try {
+                       final String PREFIX = "data:application/octet-stream;base64,";
+                       return new URI(PREFIX + base64Str);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
+               }
+
+       }
+
+       public static byte[] bytesFromDataURI(URI uri) {
+               if (!"data".equals(uri.getScheme()))
+                       throw new IllegalArgumentException("URI must have 'data' as a scheme");
+               String schemeSpecificPart = uri.getSchemeSpecificPart();
+               int commaIndex = schemeSpecificPart.indexOf(',');
+               String prefix = schemeSpecificPart.substring(0, commaIndex);
+               List<String> info = Arrays.asList(prefix.split(";"));
+               if (!info.contains("base64"))
+                       throw new IllegalArgumentException("URI must specify base64");
+
+               String base64Str = schemeSpecificPart.substring(commaIndex + 1);
+               return Base64.getDecoder().decode(base64Str);
+
+       }
+
+       public static <T> boolean isString(T t) {
+               return t instanceof String;
+       }
+
+       /** Singleton. */
+       private ContentUtils() {
+
+       }
+}
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 (file)
index 0000000..ffa28af
--- /dev/null
@@ -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
+ * <code>String<code>.
+ */
+public enum CrAttributeType implements ContentNameSupplier {
+       BOOLEAN(Boolean.class, new BooleanFormatter()), //
+       INTEGER(Integer.class, new IntegerFormatter()), //
+       LONG(Long.class, new LongFormatter()), //
+       DOUBLE(Double.class, new DoubleFormatter()), //
+       // we do not support short and float, like recent additions to Java
+       // (e.g. optional primitives)
+       DATE_TIME(Instant.class, new InstantFormatter()), //
+       UUID(UUID.class, new UuidFormatter()), //
+       ANY_URI(URI.class, new UriFormatter()), //
+       STRING(String.class, new StringFormatter()), //
+       ;
+
+       private final Class<?> clss;
+       private final AttributeFormatter<?> formatter;
+
+       private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+               this.clss = clss;
+               this.formatter = formatter;
+       }
+
+       public Class<?> getClss() {
+               return clss;
+       }
+
+       public AttributeFormatter<?> getFormatter() {
+               return formatter;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               if (equals(UUID))
+                       return CrName.CR_DEFAULT_PREFIX;
+               else
+                       return "xs";
+       }
+
+       @Override
+       public String getNamespaceURI() {
+               if (equals(UUID))
+                       return CrName.CR_NAMESPACE_URI;
+               else
+                       return XMLConstants.W3C_XML_SCHEMA_NS_URI;
+       }
+
+       public static Object parse(String str) {
+               if (str == null)
+                       throw new IllegalArgumentException("String cannot be null");
+               // order IS important
+               try {
+                       if (str.length() == 4 || str.length() == 5)
+                               return BOOLEAN.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return INTEGER.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return LONG.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return DOUBLE.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return DATE_TIME.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       if (str.length() == 36)
+                               return UUID.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       java.net.URI uri = (java.net.URI) ANY_URI.getFormatter().parse(str);
+                       if (uri.getScheme() != null)
+                               return uri;
+                       String path = uri.getPath();
+                       if (path.indexOf('/') >= 0)
+                               return uri;
+                       // if it is not clearly a path, we will consider it as a string
+                       // because their is no way to distinguish between 'any_string'
+                       // and 'any_file_name'.
+                       // Note that providing ./any_file_name would result in an equivalent URI
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+
+               // default
+               return STRING.getFormatter().parse(str);
+       }
+
+       static class BooleanFormatter implements AttributeFormatter<Boolean> {
+
+               /**
+                * @param str must be exactly equals to either 'true' or 'false' (different
+                *            contract than {@link Boolean#parseBoolean(String)}.
+                */
+               @Override
+               public Boolean parse(String str) throws IllegalArgumentException {
+                       if ("true".equals(str))
+                               return Boolean.TRUE;
+                       if ("false".equals(str))
+                               return Boolean.FALSE;
+                       throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
+               }
+       }
+
+       static class IntegerFormatter implements AttributeFormatter<Integer> {
+               @Override
+               public Integer parse(String str) throws NumberFormatException {
+                       return Integer.parseInt(str);
+               }
+       }
+
+       static class LongFormatter implements AttributeFormatter<Long> {
+               @Override
+               public Long parse(String str) throws NumberFormatException {
+                       return Long.parseLong(str);
+               }
+       }
+
+       static class DoubleFormatter implements AttributeFormatter<Double> {
+
+               @Override
+               public Double parse(String str) throws NumberFormatException {
+                       return Double.parseDouble(str);
+               }
+       }
+
+       static class InstantFormatter implements AttributeFormatter<Instant> {
+
+               @Override
+               public Instant parse(String str) throws IllegalArgumentException {
+                       try {
+                               return Instant.parse(str);
+                       } catch (DateTimeParseException e) {
+                               throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
+                       }
+               }
+       }
+
+       static class UuidFormatter implements AttributeFormatter<UUID> {
+
+               @Override
+               public UUID parse(String str) throws IllegalArgumentException {
+                       return java.util.UUID.fromString(str);
+               }
+       }
+
+       static class UriFormatter implements AttributeFormatter<URI> {
+
+               @Override
+               public URI parse(String str) throws IllegalArgumentException {
+                       try {
+                               return new URI(str);
+                       } catch (URISyntaxException e) {
+                               throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
+                       }
+               }
+
+       }
+
+       static class StringFormatter implements AttributeFormatter<String> {
+
+               @Override
+               public String parse(String str) {
+                       return str;
+               }
+
+               @Override
+               public String format(String obj) {
+                       return obj;
+               }
+
+       }
+
+}
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 (file)
index 0000000..099be9f
--- /dev/null
@@ -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 (file)
index 0000000..fa95daf
--- /dev/null
@@ -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<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
+       private final FS fs;
+       /** null for non absolute paths */
+       private final ST fileStore;
+
+       private final String[] segments;// null means root
+       private final boolean absolute;
+
+       private final String separator;
+
+       // optim
+       private final int hashCode;
+
+       public AbstractFsPath(FS filesSystem, String path) {
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               this.fs = filesSystem;
+               this.separator = fs.getSeparator();
+               // TODO deal with both path and separator being empty strings
+               if (path.equals(separator)) {// root
+                       this.segments = null;
+                       this.absolute = true;
+                       this.hashCode = 0;
+                       this.fileStore = fs.getBaseFileStore();
+                       return;
+               } else if (path.equals("")) {// empty path
+                       this.segments = new String[] { "" };
+                       this.absolute = false;
+                       this.hashCode = "".hashCode();
+                       this.fileStore = null;
+                       return;
+               }
+
+               this.absolute = path.startsWith(toStringRoot());
+
+               String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
+                               path.endsWith(separator) ? path.length() - separator.length() : path.length());
+               this.segments = trimmedPath.split(separator);
+               // clean up
+               for (int i = 0; i < this.segments.length; i++) {
+                       this.segments[i] = cleanUpSegment(this.segments[i]);
+               }
+               this.hashCode = this.segments[this.segments.length - 1].hashCode();
+
+               this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
+       }
+
+       protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
+               this.segments = segments;
+               this.absolute = absolute;
+               this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
+               this.separator = filesSystem.getSeparator();
+//             super(path, path == null ? true : absolute, filesSystem.getSeparator());
+//             assert path == null ? absolute == true : true;
+               this.fs = filesSystem;
+//             this.path = path;
+//             this.absolute = path == null ? true : absolute;
+               if (isAbsolute() && fileStore == null)
+                       throw new IllegalArgumentException("Absolute path requires a file store");
+               if (!isAbsolute() && fileStore != null)
+                       throw new IllegalArgumentException("A file store should not be provided for a relative path");
+               this.fileStore = fileStore;
+               assert !(absolute && fileStore == null);
+       }
+
+       protected Path retrieve(String path) {
+               return getFileSystem().getPath(path);
+       }
+
+       @Override
+       public FS getFileSystem() {
+               return fs;
+       }
+
+       public ST getFileStore() {
+               return fileStore;
+       }
+
+       @Override
+       public boolean isAbsolute() {
+               return absolute;
+       }
+
+       @Override
+       public URI toUri() {
+               try {
+                       return new URI(fs.provider().getScheme(), toString(), null);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot create URI for " + toString(), e);
+               }
+       }
+
+       @Override
+       public Path toAbsolutePath() {
+               if (isAbsolute())
+                       return this;
+               // FIXME it doesn't seem right
+               return newInstance(getSegments(), true);
+       }
+
+       @Override
+       public Path toRealPath(LinkOption... options) throws IOException {
+               return this;
+       }
+
+       @Override
+       public File toFile() {
+               throw new UnsupportedOperationException();
+       }
+
+       /*
+        * PATH OPERATIONS
+        */
+       public final Path resolveSibling(Path other) {
+               if (other == null)
+                       throw new NullPointerException();
+               Path parent = getParent();
+               return (parent == null) ? other : parent.resolve(other);
+       }
+
+       @Override
+       public final Path resolveSibling(String other) {
+               return resolveSibling(getFileSystem().getPath(other));
+       }
+
+       public final Path resolve(String other) {
+               return resolve(retrieve(other));
+       }
+
+       public boolean startsWith(Path other) {
+               return toString().startsWith(other.toString());
+       }
+
+       public boolean endsWith(Path other) {
+               return toString().endsWith(other.toString());
+       }
+
+       @Override
+       public Path normalize() {
+               // always normalized
+               return this;
+       }
+
+       @Override
+       public final Iterator<Path> iterator() {
+               return new Iterator<Path>() {
+                       private int i = 0;
+
+                       @Override
+                       public boolean hasNext() {
+                               return (i < getNameCount());
+                       }
+
+                       @Override
+                       public Path next() {
+                               if (i < getNameCount()) {
+                                       Path result = getName(i);
+                                       i++;
+                                       return result;
+                               } else {
+                                       throw new NoSuchElementException();
+                               }
+                       }
+
+                       @Override
+                       public void remove() {
+                               throw new UnsupportedOperationException();
+                       }
+               };
+       }
+
+       @Override
+       public int compareTo(Path other) {
+               return toString().compareTo(other.toString());
+       }
+
+       public Path resolve(Path other) {
+               AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
+               if (otherPath.isAbsolute())
+                       return other;
+               String[] newPath;
+               if (isRoot()) {
+                       newPath = new String[otherPath.segments.length];
+                       System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
+               } else {
+                       newPath = new String[segments.length + otherPath.segments.length];
+                       System.arraycopy(segments, 0, newPath, 0, segments.length);
+                       System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
+               }
+               if (!absolute)
+                       return newInstance(newPath, absolute);
+               else {
+                       return newInstance(toString(newPath));
+               }
+       }
+
+       public Path relativize(Path other) {
+               if (equals(other))
+                       return newInstance("");
+               if (other.toString().startsWith(this.toString())) {
+                       String p1 = toString();
+                       String p2 = other.toString();
+                       String relative = p2.substring(p1.length(), p2.length());
+                       if (relative.charAt(0) == '/')
+                               relative = relative.substring(1);
+                       return newInstance(relative);
+               }
+               throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+       }
+
+       /*
+        * FACTORIES
+        */
+       protected abstract AbstractFsPath<FS, ST> newInstance(String path);
+
+       protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
+
+       /*
+        * CUSTOMISATIONS
+        */
+       protected String toStringRoot() {
+               return separator;
+       }
+
+       protected String cleanUpSegment(String segment) {
+               return segment;
+       }
+
+       protected boolean isRoot() {
+               return segments == null;
+       }
+
+       protected boolean isEmpty() {
+               return segments.length == 1 && "".equals(segments[0]);
+       }
+
+       /*
+        * PATH OPERATIONS
+        */
+       public AbstractFsPath<FS, ST> getRoot() {
+               return newInstance(toStringRoot());
+       }
+
+       public AbstractFsPath<FS, ST> getParent() {
+               if (isRoot())
+                       return null;
+               // FIXME empty path?
+               if (segments.length == 1)// first level
+                       return newInstance(toStringRoot());
+               String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
+               if (!absolute)
+                       return newInstance(parentPath, absolute);
+               else
+                       return newInstance(toString(parentPath));
+       }
+
+       public AbstractFsPath<FS, ST> getFileName() {
+               if (isRoot())
+                       return null;
+               return newInstance(segments[segments.length - 1]);
+       }
+
+       public int getNameCount() {
+               if (isRoot())
+                       return 0;
+               return segments.length;
+       }
+
+       public AbstractFsPath<FS, ST> getName(int index) {
+               if (isRoot())
+                       return null;
+               return newInstance(segments[index]);
+       }
+
+       public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
+               if (isRoot())
+                       return null;
+               String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
+               return newInstance(parentPath, false);
+       }
+
+       public boolean startsWith(String other) {
+               return toString().startsWith(other);
+       }
+
+       public boolean endsWith(String other) {
+               return toString().endsWith(other);
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected String toString(String[] path) {
+               if (isRoot())
+                       return toStringRoot();
+               StringBuilder sb = new StringBuilder();
+               if (isAbsolute())
+                       sb.append(separator);
+               for (int i = 0; i < path.length; i++) {
+                       if (i != 0)
+                               sb.append(separator);
+                       sb.append(path[i]);
+               }
+               return sb.toString();
+       }
+
+       @Override
+       public String toString() {
+               return toString(segments);
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof AbstractFsPath))
+                       return false;
+               AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
+
+               if (isRoot()) {// root
+                       if (other.isRoot())// root
+                               return true;
+                       else
+                               return false;
+               } else {
+                       if (other.isRoot())// root
+                               return false;
+               }
+               // non root
+               if (segments.length != other.segments.length)
+                       return false;
+               for (int i = 0; i < segments.length; i++) {
+                       if (!segments[i].equals(other.segments[i]))
+                               return false;
+               }
+               return true;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return newInstance(toString());
+       }
+
+       /*
+        * GETTERS / SETTERS
+        */
+       protected String[] getSegments() {
+               return segments;
+       }
+
+       protected String getSeparator() {
+               return separator;
+       }
+
+       /*
+        * UNSUPPORTED
+        */
+       @Override
+       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+}
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 (file)
index 0000000..1294766
--- /dev/null
@@ -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 (file)
index 0000000..3bf10f6
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.acr.fs;
+
+import java.nio.file.FileSystem;
+
+public abstract class AbstractFsSystem<ST extends AbstractFsStore> extends FileSystem {
+       public abstract ST getBaseFileStore();
+
+       public abstract ST getFileStore(String path);
+}
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 (file)
index 0000000..6bfc7cd
--- /dev/null
@@ -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<QName, Object> implements Content {
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+       protected abstract Iterable<QName> keys();
+
+       protected abstract void removeAttr(QName key);
+
+       @Override
+       public Set<Entry<QName, Object>> entrySet() {
+               Set<Entry<QName, Object>> result = new AttrSet();
+               return result;
+       }
+
+       @Override
+       public Class<?> getType(QName key) {
+               return String.class;
+       }
+
+       @Override
+       public boolean isMultiple(QName key) {
+               return false;
+       }
+
+       @Override
+       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
+               Object value = get(key);
+               if (value == null)
+                       return null;
+               if (value instanceof List) {
+                       try {
+                               List<A> res = (List<A>) value;
+                               return Optional.of(res);
+                       } catch (ClassCastException e) {
+                               List<A> res = new ArrayList<>();
+                               List<?> lst = (List<?>) value;
+                               try {
+                                       for (Object o : lst) {
+                                               A item = (A) o;
+                                               res.add(item);
+                                       }
+                                       return Optional.of(res);
+                               } catch (ClassCastException e1) {
+                                       return Optional.empty();
+                               }
+                       }
+               } else {// singleton
+                       try {
+                               A res = (A) value;
+                               return Optional.of(Collections.singletonList(res));
+                       } catch (ClassCastException e) {
+                               return Optional.empty();
+                       }
+               }
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public String getPath() {
+               List<Content> ancestors = new ArrayList<>();
+               collectAncestors(ancestors, this);
+               StringBuilder path = new StringBuilder();
+               for (Content c : ancestors) {
+                       QName name = c.getName();
+                       // FIXME
+                       if (!CrName.ROOT.get().equals(name))
+                               path.append('/').append(name);
+               }
+               return path.toString();
+       }
+
+       private void collectAncestors(List<Content> ancestors, Content content) {
+               if (content == null)
+                       return;
+               ancestors.add(0, content);
+               collectAncestors(ancestors, content.getParent());
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+               // check whether clss is Object.class
+               return clss.isAssignableFrom(Object.class);
+       }
+
+       @Override
+       public String toString() {
+               return "content " + getPath();
+       }
+
+       /*
+        * SUB CLASSES
+        */
+
+       class AttrSet extends AbstractSet<Entry<QName, Object>> {
+
+               @Override
+               public Iterator<Entry<QName, Object>> iterator() {
+                       final Iterator<QName> keys = keys().iterator();
+                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
+
+                               QName key = null;
+
+                               @Override
+                               public boolean hasNext() {
+                                       return keys.hasNext();
+                               }
+
+                               @Override
+                               public Entry<QName, Object> next() {
+                                       key = keys.next();
+                                       // TODO check type
+                                       Optional<?> value = get(key, Object.class);
+                                       assert !value.isEmpty();
+                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
+                                       return entry;
+                               }
+
+                               @Override
+                               public void remove() {
+                                       if (key != null) {
+                                               AbstractContent.this.removeAttr(key);
+                                       } else {
+                                               throw new IllegalStateException("Iteration has not started");
+                                       }
+                               }
+
+                       };
+                       return it;
+               }
+
+               @Override
+               public int size() {
+                       int count = 0;
+                       for (QName key : keys()) {
+                               count++;
+                       }
+                       return count;
+               }
+
+       }
+}
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 (file)
index 0000000..d83cf49
--- /dev/null
@@ -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 (file)
index 0000000..d9fc781
--- /dev/null
@@ -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 (file)
index 0000000..c3052c3
--- /dev/null
@@ -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 (file)
index 0000000..f90d674
--- /dev/null
@@ -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<String> findPrefixes(String namespaceURI);
+
+       /** To be overridden for optimisation, as it will be called a lot */
+       default String findPrefix(String namespaceURI) {
+               Set<String> prefixes = findPrefixes(namespaceURI);
+               if (prefixes.isEmpty())
+                       return null;
+               return prefixes.iterator().next();
+       }
+
+       @Override
+       default String getNamespaceURI(String prefix) {
+               String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
+                       return XMLConstants.NULL_NS_URI;
+               namespaceURI = findNamespace(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       @Override
+       default String getPrefix(String namespaceURI) {
+               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return XMLConstants.DEFAULT_NS_PREFIX;
+               return findPrefix(namespaceURI);
+       }
+
+       @Override
+       default Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> standard = ContentNameSupplier.getStandardPrefixes(namespaceURI);
+               if (standard != null)
+                       return standard;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
+               Set<String> prefixes = findPrefixes(namespaceURI);
+               assert prefixes != null;
+               return prefixes.iterator();
+       }
+
+}
diff --git a/org.argeo.api.cms/.classpath b/org.argeo.api.cms/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.api.cms/.project b/org.argeo.api.cms/.project
new file mode 100644 (file)
index 0000000..17631c8
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.cms</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.api.cms/META-INF/.gitignore b/org.argeo.api.cms/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -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 (file)
index 0000000..51c4e66
--- /dev/null
@@ -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 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -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 (file)
index 0000000..b1cfca0
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>argeo-commons</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.api.cms</artifactId>
+       <name>CMS API</name>
+       <packaging>jar</packaging>
+</project>
\ No newline at end of file
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 (file)
index 0000000..63ee348
--- /dev/null
@@ -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 (file)
index 0000000..30b3d81
--- /dev/null
@@ -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 (file)
index 0000000..761191e
--- /dev/null
@@ -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<String> getUiNames();
+
+       CmsUi initUi(Object uiParent);
+
+       void refreshUi(CmsUi cmsUi, String state);
+
+       void setState(CmsUi cmsUi, String state);
+
+       CmsTheme getTheme(String uiName);
+
+       boolean allThemesAvailable();
+
+       void addCmsAppListener(CmsAppListener listener);
+
+       void removeCmsAppListener(CmsAppListener listener);
+}
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 (file)
index 0000000..55fcec5
--- /dev/null
@@ -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 (file)
index 0000000..decea35
--- /dev/null
@@ -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 (file)
index 0000000..8fe9846
--- /dev/null
@@ -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 (file)
index 0000000..fa26b25
--- /dev/null
@@ -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<Locale> 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 (file)
index 0000000..5893d2e
--- /dev/null
@@ -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<String, Object> props);
+
+       Dictionary<String, Object> 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 (file)
index 0000000..2deca01
--- /dev/null
@@ -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 (file)
index 0000000..b5dccbe
--- /dev/null
@@ -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 (file)
index 0000000..8c637b8
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.api.cms;
+
+import java.io.InputStream;
+
+/** Read and write access to images. */
+public interface CmsImageManager<V, M> {
+       /** Load image in control */
+       public Boolean load(M node, V control, Cms2DSize size);
+
+       /** @return (0,0) if not available */
+       public Cms2DSize getImageSize(M node);
+
+       /**
+        * The related &lt;img&gt; tag, with src, width and height set.
+        * 
+        * @return null if not available
+        */
+       public String getImageTag(M node);
+
+       /**
+        * The related &lt;img&gt; 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 (file)
index 0000000..206cfd6
--- /dev/null
@@ -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 (file)
index 0000000..18d53ce
--- /dev/null
@@ -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 (file)
index 0000000..0e47789
--- /dev/null
@@ -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 (file)
index 0000000..ed8698f
--- /dev/null
@@ -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 (file)
index 0000000..8444e2f
--- /dev/null
@@ -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 (file)
index 0000000..50c3b1f
--- /dev/null
@@ -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
+        * <code>null</code> if not found
+        */
+       InputStream getResourceAsStream(String resourceName) throws IOException;
+
+       /** Relative paths to standard web CSS. */
+       Set<String> getWebCssPaths();
+
+       /** Relative paths to RAP specific CSS. */
+       Set<String> getRapCssPaths();
+
+       /** Relative paths to SWT specific CSS. */
+       Set<String> getSwtCssPaths();
+
+       /** Relative paths to images such as icons. */
+       Set<String> getImagesPaths();
+
+       /** Relative paths to fonts. */
+       Set<String> getFontsPaths();
+
+       /** Tags to be added to the header section of the HTML page. */
+       String getHtmlHeaders();
+
+       /** The HTML body to use. */
+       String getBodyHtml();
+
+       /** The default icon size (typically the smallest). */
+       Integer getDefaultIconSize();
+
+       /** Loads one of the relative path provided by the other methods. */
+       InputStream loadPath(String path) throws IOException;
+
+}
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 (file)
index 0000000..fd91c6e
--- /dev/null
@@ -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 (file)
index 0000000..c7ca1e9
--- /dev/null
@@ -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<String, Object> properties) {
+
+       }
+
+       /**
+        * Convenience methods for when {@link #sendEvent(String, Map)} only requires
+        * one single parameter.
+        */
+       default void sendEvent(String topic, String param, Object value) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(param, value);
+               sendEvent(topic, properties);
+       }
+
+       default void applyStyles(Object widget) {
+
+       }
+
+       default <T> T doAs(PrivilegedAction<T> action) {
+               throw new UnsupportedOperationException();
+       }
+
+       default Void runAs(Runnable runnable) {
+               return doAs(new PrivilegedAction<Void>() {
+
+                       @Override
+                       public Void run() {
+                               if (runnable != null)
+                                       runnable.run();
+                               return null;
+                       }
+               });
+       }
+
+       default void stateChanged(String state, String title) {
+       }
+
+       default CmsSession getCmsSession() {
+               throw new UnsupportedOperationException();
+       }
+
+       default Object getData(String key) {
+               throw new UnsupportedOperationException();
+       }
+
+       @SuppressWarnings("unchecked")
+       default <T> T getUiContext(Class<T> clss) {
+               return (T) getData(clss.getName());
+       }
+
+       default <T> void setUiContext(Class<T> clss, T instance) {
+               setData(clss.getName(), instance);
+       }
+
+       default void setData(String key, Object value) {
+               throw new UnsupportedOperationException();
+       }
+
+}
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 (file)
index 0000000..bc12bcb
--- /dev/null
@@ -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 (file)
index 0000000..c1aa600
--- /dev/null
@@ -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<V, M, W> extends BiFunction<V, M, W> {
+       W createUiPart(V parent, M context);
+       
+       /**
+        * Whether this parent view is supported.
+        * 
+        * @return true by default.
+        */
+       default boolean isViewSupported(V parent) {
+               return true;
+       }
+
+       /**
+        * Whether this context is supported.
+        * 
+        * @return true by default.
+        */
+       default boolean isModelSupported(M context) {
+               return true;
+       }
+
+       default W apply(V parent, M context) {
+               if (!isViewSupported(parent))
+                       throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
+               if (!isModelSupported(context))
+                       throw new IllegalArgumentException("Model context " + context + "is not supported.");
+               return createUiPart(parent, context);
+       }
+
+       default W createUiPart(V parent) {
+               return createUiPart(parent, null);
+       }
+}
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 (file)
index 0000000..fb99178
--- /dev/null
@@ -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 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.api/.project b/org.argeo.api/.project
deleted file mode 100644 (file)
index a396aad..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.api</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.api/META-INF/.gitignore b/org.argeo.api/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /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 (file)
index a8a1e4f..0000000
+++ /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 (file)
index 34d2e4d..0000000
+++ /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 (file)
index 82ad855..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-       <modelVersion>4.0.0</modelVersion>
-       <parent>
-               <groupId>org.argeo.commons</groupId>
-               <artifactId>argeo-commons</artifactId>
-               <version>2.3-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.api</artifactId>
-       <name>CMS APIs</name>
-       <packaging>jar</packaging>
-       <dependencies>
-       </dependencies>
-</project>
\ No newline at end of file
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 (file)
index 63ee348..0000000
+++ /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 (file)
index 30b3d81..0000000
+++ /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 (file)
index 761191e..0000000
+++ /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<String> getUiNames();
-
-       CmsUi initUi(Object uiParent);
-
-       void refreshUi(CmsUi cmsUi, String state);
-
-       void setState(CmsUi cmsUi, String state);
-
-       CmsTheme getTheme(String uiName);
-
-       boolean allThemesAvailable();
-
-       void addCmsAppListener(CmsAppListener listener);
-
-       void removeCmsAppListener(CmsAppListener listener);
-}
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 (file)
index 55fcec5..0000000
+++ /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 (file)
index decea35..0000000
+++ /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 (file)
index 8fe9846..0000000
+++ /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 (file)
index fa26b25..0000000
+++ /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<Locale> 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 (file)
index 5893d2e..0000000
+++ /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<String, Object> props);
-
-       Dictionary<String, Object> 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 (file)
index 2deca01..0000000
+++ /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 (file)
index b5dccbe..0000000
+++ /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 (file)
index 8c637b8..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.api.cms;
-
-import java.io.InputStream;
-
-/** Read and write access to images. */
-public interface CmsImageManager<V, M> {
-       /** Load image in control */
-       public Boolean load(M node, V control, Cms2DSize size);
-
-       /** @return (0,0) if not available */
-       public Cms2DSize getImageSize(M node);
-
-       /**
-        * The related &lt;img&gt; tag, with src, width and height set.
-        * 
-        * @return null if not available
-        */
-       public String getImageTag(M node);
-
-       /**
-        * The related &lt;img&gt; 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 (file)
index 206cfd6..0000000
+++ /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 (file)
index 18d53ce..0000000
+++ /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 (file)
index 0e47789..0000000
+++ /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 (file)
index ed8698f..0000000
+++ /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 (file)
index 8444e2f..0000000
+++ /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 (file)
index 50c3b1f..0000000
+++ /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
-        * <code>null</code> if not found
-        */
-       InputStream getResourceAsStream(String resourceName) throws IOException;
-
-       /** Relative paths to standard web CSS. */
-       Set<String> getWebCssPaths();
-
-       /** Relative paths to RAP specific CSS. */
-       Set<String> getRapCssPaths();
-
-       /** Relative paths to SWT specific CSS. */
-       Set<String> getSwtCssPaths();
-
-       /** Relative paths to images such as icons. */
-       Set<String> getImagesPaths();
-
-       /** Relative paths to fonts. */
-       Set<String> getFontsPaths();
-
-       /** Tags to be added to the header section of the HTML page. */
-       String getHtmlHeaders();
-
-       /** The HTML body to use. */
-       String getBodyHtml();
-
-       /** The default icon size (typically the smallest). */
-       Integer getDefaultIconSize();
-
-       /** Loads one of the relative path provided by the other methods. */
-       InputStream loadPath(String path) throws IOException;
-
-}
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 (file)
index fd91c6e..0000000
+++ /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 (file)
index c7ca1e9..0000000
+++ /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<String, Object> properties) {
-
-       }
-
-       /**
-        * Convenience methods for when {@link #sendEvent(String, Map)} only requires
-        * one single parameter.
-        */
-       default void sendEvent(String topic, String param, Object value) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(param, value);
-               sendEvent(topic, properties);
-       }
-
-       default void applyStyles(Object widget) {
-
-       }
-
-       default <T> T doAs(PrivilegedAction<T> action) {
-               throw new UnsupportedOperationException();
-       }
-
-       default Void runAs(Runnable runnable) {
-               return doAs(new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               if (runnable != null)
-                                       runnable.run();
-                               return null;
-                       }
-               });
-       }
-
-       default void stateChanged(String state, String title) {
-       }
-
-       default CmsSession getCmsSession() {
-               throw new UnsupportedOperationException();
-       }
-
-       default Object getData(String key) {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings("unchecked")
-       default <T> T getUiContext(Class<T> clss) {
-               return (T) getData(clss.getName());
-       }
-
-       default <T> void setUiContext(Class<T> clss, T instance) {
-               setData(clss.getName(), instance);
-       }
-
-       default void setData(String key, Object value) {
-               throw new UnsupportedOperationException();
-       }
-
-}
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 (file)
index bc12bcb..0000000
+++ /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 (file)
index c1aa600..0000000
+++ /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<V, M, W> extends BiFunction<V, M, W> {
-       W createUiPart(V parent, M context);
-       
-       /**
-        * Whether this parent view is supported.
-        * 
-        * @return true by default.
-        */
-       default boolean isViewSupported(V parent) {
-               return true;
-       }
-
-       /**
-        * Whether this context is supported.
-        * 
-        * @return true by default.
-        */
-       default boolean isModelSupported(M context) {
-               return true;
-       }
-
-       default W apply(V parent, M context) {
-               if (!isViewSupported(parent))
-                       throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
-               if (!isModelSupported(context))
-                       throw new IllegalArgumentException("Model context " + context + "is not supported.");
-               return createUiPart(parent, context);
-       }
-
-       default W createUiPart(V parent) {
-               return createUiPart(parent, null);
-       }
-}
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 (file)
index fb99178..0000000
+++ /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 (file)
index a628cda..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.argeo.api.gcr;
-
-/**
- * An attribute type MUST consistently parse a string to an object so that
- * <code>parse(obj.toString()).equals(obj)</code> is verified.
- * {@link #format(Object)} can be overridden to provide more efficient
- * implementations but the returned
- * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
- * is verified.
- */
-public interface AttributeFormatter<T> {
-       /** Parses a String to a Java object. */
-       T parse(String str) throws IllegalArgumentException;
-
-       /** Default implementation returns {@link Object#toString()} on the argument. */
-       default String format(T obj) {
-               return obj.toString();
-       }
-}
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 (file)
index f979357..0000000
+++ /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<String> res = new ArrayList<>();
-               if (separator != null) {
-                       StringTokenizer st = new StringTokenizer(str, separator.toString());
-                       while (st.hasMoreTokens()) {
-                               res.add(st.nextToken().toLowerCase());
-                       }
-               } else {
-                       // single
-                       String strLowerCase = str.toLowerCase();
-                       if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
-                               return new String[] { strLowerCase };
-
-                       // CAML
-                       StringBuilder current = null;
-                       for (char c : str.toCharArray()) {
-                               if (Character.isUpperCase(c)) {
-                                       if (current != null)
-                                               res.add(current.toString());
-                                       current = new StringBuilder();
-                               }
-                               if (current == null)// first char is lower case
-                                       current = new StringBuilder();
-                               current.append(Character.toLowerCase(c));
-                       }
-                       res.add(current.toString());
-               }
-               return res.toArray(new String[res.size()]);
-       }
-
-       private static void checkNo(String str, Character c) {
-               if (str.indexOf(c) >= 0) {
-                       throw new IllegalArgumentException("Only one kind of sperator is allowed");
-               }
-       }
-
-       private static int hashCode(String[] parts) {
-               int hashCode = 0;
-               for (String part : parts) {
-                       hashCode = hashCode + part.hashCode();
-               }
-               return hashCode;
-       }
-
-       static boolean smokeTests() {
-               CompositeString plainName = new CompositeString("NAME");
-               assert "name".equals(plainName.toString());
-               assert "NAME".equals(plainName.toString(UNDERSCORE, true));
-               assert "name".equals(plainName.toString(UNDERSCORE, false));
-               assert "name".equals(plainName.toStringCaml(false));
-               assert "Name".equals(plainName.toStringCaml(true));
-
-               CompositeString camlName = new CompositeString("myComplexName");
-
-               assert new CompositeString("my-complex-name").equals(camlName);
-               assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
-               assert new CompositeString("My complex Name").equals(camlName);
-               assert new CompositeString("MyComplexName").equals(camlName);
-
-               assert "my-complex-name".equals(camlName.toString());
-               assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
-               assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
-               assert "myComplexName".equals(camlName.toStringCaml(false));
-               assert "MyComplexName".equals(camlName.toStringCaml(true));
-
-               return CompositeString.class.desiredAssertionStatus();
-       }
-
-       public static void main(String[] args) {
-               System.out.println(smokeTests());
-       }
-}
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 (file)
index fa4d507..0000000
+++ /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<Content>, Map<QName, Object> {
-
-       QName getName();
-
-       String getPath();
-
-       Content getParent();
-
-       /*
-        * ATTRIBUTES OPERATIONS
-        */
-
-       <A> Optional<A> get(QName key, Class<A> clss);
-
-       default Object get(String key) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
-       }
-
-       default Object put(String key, Object value) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
-       }
-
-       default Object remove(String key) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
-       }
-
-       Class<?> getType(QName key);
-
-       boolean isMultiple(QName key);
-
-       <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
-
-       default <A> List<A> getMultiple(QName key) {
-               Class<A> type;
-               try {
-                       type = (Class<A>) getType(key);
-               } catch (ClassCastException e) {
-                       throw new IllegalArgumentException("Requested type is not the default type");
-               }
-               Optional<List<A>> res = getMultiple(key, type);
-               if (res == null)
-                       return null;
-               else {
-                       if (res.isEmpty())
-                               throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
-                       return res.get();
-               }
-       }
-
-       /*
-        * CONTENT OPERATIONS
-        */
-       Content add(QName name, QName... classes);
-
-       default Content add(String name, QName... classes) {
-               if (name.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + name + " has a prefix");
-               return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
-       }
-
-       void remove();
-
-       /*
-        * DEFAULT METHODS
-        */
-       default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
-               throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
-       }
-
-       default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
-               throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
-       }
-
-       /*
-        * CONVENIENCE METHODS
-        */
-//     default String attr(String key) {
-//             return get(key, String.class);
-//     }
-//
-//     default String attr(Object key) {
-//             return key != null ? attr(key.toString()) : attr(null);
-//     }
-//
-//     default <A> A get(Object key, Class<A> clss) {
-//             return key != null ? get(key.toString(), clss) : get(null, clss);
-//     }
-
-       /*
-        * EXPERIMENTAL UNSUPPORTED
-        */
-       default boolean hasText() {
-               return false;
-       }
-
-       default String getText() {
-               throw new UnsupportedOperationException();
-       }
-
-}
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 (file)
index 0e97a24..0000000
+++ /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 (file)
index b230425..0000000
+++ /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<String, UUID> namespaceUuids = Collections.synchronizedMap(new TreeMap<>());
-       private final static Map<String, UUID> nameUuids = Collections.synchronizedMap(new TreeMap<>());
-
-       static {
-               assert NULL_NS_UUID.equals(nameUUIDv3(XMLNS_UUID, XMLConstants.NULL_NS_URI.getBytes(UTF_8)));
-       }
-
-//     private final UUID uuid;
-
-       public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
-               super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
-       }
-
-       private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
-               Objects.requireNonNull(nsContext, "Namespace context cannot be null");
-               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
-               String prefix = nsContext.getNamespaceURI(namespaceURI);
-               if (prefix == null)
-                       throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
-               return prefix;
-       }
-
-       ContentName(String namespaceURI, String localPart, String prefix) {
-               super(namespaceURI, localPart, prefix);
-       }
-
-       public ContentName(String localPart) {
-               super(XMLConstants.NULL_NS_URI, localPart, XMLConstants.DEFAULT_NS_PREFIX);
-       }
-
-       public ContentName(QName qName, NamespaceContext nsContext) {
-               this(qName.getNamespaceURI(), qName.getLocalPart(), nsContext);
-       }
-
-       public String toQNameString() {
-               return super.toString();
-       }
-
-       public String toPrefixedString() {
-               return toPrefixedString(this);
-       }
-
-       /*
-        * OBJECT METHOS
-        */
-
-       @Override
-       public String toString() {
-               return toPrefixedString();
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               return new ContentName(getNamespaceURI(), getLocalPart(), getPrefix());
-       }
-
-       public static String toPrefixedString(QName name) {
-               String prefix = name.getPrefix();
-               assert prefix != null;
-               return "".equals(prefix) ? name.getLocalPart() : prefix + ":" + name.getLocalPart();
-       }
-//     ContentNamespace getNamespace();
-//
-//     String getName();
-
-       public static UUID namespaceUuid(String namespaceURI) {
-               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
-                       return NULL_NS_UUID;
-               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
-               synchronized (namespaceUuids) {
-                       UUID namespaceUuid = namespaceUuids.get(namespaceURI);
-                       if (namespaceUuid == null) {
-                               namespaceUuid = nameUUIDv3(ContentName.XMLNS_UUID,
-                                               namespaceURI.toString().getBytes(StandardCharsets.UTF_8));
-                               namespaceUuids.put(namespaceURI, namespaceUuid);
-                       }
-                       return namespaceUuid;
-               }
-       }
-
-       public static UUID nameUuid(String namespaceURI, QName name) {
-               return nameUuid(name.getNamespaceURI(), name.getLocalPart());
-       }
-
-       public static UUID nameUuid(String namespaceURI, String name) {
-               Objects.requireNonNull(namespaceURI, "Namespace cannot be null");
-               Objects.requireNonNull(name, "Name cannot be null");
-               synchronized (nameUuids) {
-                       String key = XMLConstants.NULL_NS_URI.equals(namespaceURI) ? name : "{" + namespaceURI + "}" + name;
-                       UUID nameUuid = nameUuids.get(key);
-                       if (nameUuid == null) {
-                               UUID namespaceUuid = namespaceUuid(namespaceURI);
-                               nameUuid = nameUUIDv3(namespaceUuid, name.getBytes(StandardCharsets.UTF_8));
-                               namespaceUuids.put(key, nameUuid);
-                       }
-                       return nameUuid;
-               }
-       }
-
-       /*
-        * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
-        */
-       static UUID nameUUIDv3(UUID namespace, byte[] name) {
-               byte[] arr = new byte[name.length + 16];
-               ContentName.copyUuidBytes(namespace, arr, 0);
-               System.arraycopy(name, 0, arr, 16, name.length);
-               return UUID.nameUUIDFromBytes(arr);
-       }
-
-       static void copyUuidBytes(UUID uuid, byte[] arr, int offset) {
-               long msb = uuid.getMostSignificantBits();
-               long lsb = uuid.getLeastSignificantBits();
-               assert arr.length >= 16 + offset;
-               for (int i = offset; i < 8 + offset; i++)
-                       arr[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
-               for (int i = 8 + offset; i < 16 + offset; i++)
-                       arr[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
-       }
-
-       /*
-        * UTILITIES
-        */
-
-       public static boolean contains(QName[] classes, QName name) {
-               for (QName clss : classes) {
-                       if (clss.equals(name))
-                               return true;
-               }
-               return false;
-       }
-}
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 (file)
index 0c10201..0000000
+++ /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<ContentName>, NamespaceContext {
-       String name();
-
-       String getNamespaceURI();
-
-       String getDefaultPrefix();
-
-       @Override
-       default ContentName get() {
-               return toContentName();
-       }
-
-       default ContentName toContentName() {
-               CompositeString cs = new CompositeString(name());
-               String camlName = cs.toStringCaml(false);
-               return new ContentName(getNamespaceURI(), camlName, this);
-       }
-
-//     default String getNamespaceURI() {
-//             return XMLConstants.NULL_NS_URI;
-//     }
-//
-//     default String getDefaultPrefix() {
-//             return XMLConstants.DEFAULT_NS_PREFIX;
-//     }
-
-//     static ContentName toContentName(String namespaceURI, String localName, String prefix) {
-//             CompositeString cs = new CompositeString(localName);
-//             String camlName = cs.toStringCaml(false);
-//             return new ContentName(namespaceURI, camlName, this);
-//     }
-
-       /*
-        * NAMESPACE CONTEXT
-        */
-
-       @Override
-       default String getNamespaceURI(String prefix) {
-               String namespaceURI = getStandardNamespaceURI(prefix);
-               if (namespaceURI != null)
-                       return namespaceURI;
-               if (prefix.equals(getDefaultPrefix()))
-                       return getNamespaceURI();
-               return XMLConstants.NULL_NS_URI;
-       }
-
-       @Override
-       default String getPrefix(String namespaceURI) {
-               String prefix = getStandardPrefix(namespaceURI);
-               if (prefix != null)
-                       return prefix;
-               if (namespaceURI.equals(getNamespaceURI()))
-                       return getDefaultPrefix();
-               return null;
-       }
-
-       @Override
-       default Iterator<String> getPrefixes(String namespaceURI) {
-               Iterator<String> it = getStandardPrefixes(namespaceURI);
-               if (it != null)
-                       return it;
-               if (namespaceURI.equals(getNamespaceURI()))
-                       return Collections.singleton(getDefaultPrefix()).iterator();
-               return Collections.emptyIterator();
-       }
-
-       /*
-        * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
-        */
-       static String getStandardPrefix(String namespaceURI) {
-               if (namespaceURI == null)
-                       throw new IllegalArgumentException("Namespace URI cannot be null");
-               if (XMLConstants.XML_NS_URI.equals(namespaceURI))
-                       return XMLConstants.XML_NS_PREFIX;
-               else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
-                       return XMLConstants.XMLNS_ATTRIBUTE;
-               return null;
-       }
-
-       static Iterator<String> getStandardPrefixes(String namespaceURI) {
-               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
-               if (prefix == null)
-                       return null;
-               return Collections.singleton(prefix).iterator();
-       }
-
-       static String getStandardNamespaceURI(String prefix) {
-               if (prefix == null)
-                       throw new IllegalArgumentException("Prefix cannot be null");
-               if (XMLConstants.XML_NS_PREFIX.equals(prefix))
-                       return XMLConstants.XML_NS_URI;
-               else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
-                       return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
-               return null;
-       }
-
-}
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 (file)
index 8a09093..0000000
+++ /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 (file)
index 0807075..0000000
+++ /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> {
-       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 (file)
index fa7195e..0000000
+++ /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 (file)
index 6c80189..0000000
+++ /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 (file)
index c9c90bc..0000000
+++ /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 (file)
index cf51155..0000000
+++ /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<Content, Integer> doIt) {
-               traverse(content, doIt, 0);
-       }
-
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
-               doIt.accept(content, currentDepth);
-               int nextDepth = currentDepth + 1;
-               for (Content child : content) {
-                       traverse(child, doIt, nextDepth);
-               }
-       }
-
-       public static void print(Content content, PrintStream out, int depth, boolean printText) {
-               StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < depth; i++) {
-                       sb.append("  ");
-               }
-               String prefix = sb.toString();
-               out.println(prefix + content.getName());
-               for (QName key : content.keySet()) {
-                       out.println(prefix + " " + key + "=" + content.get(key));
-               }
-               if (printText) {
-                       if (content.hasText()) {
-                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
-                       }
-               }
-       }
-
-       public static URI bytesToDataURI(byte[] arr) {
-               String base64Str = Base64.getEncoder().encodeToString(arr);
-               try {
-                       final String PREFIX = "data:application/octet-stream;base64,";
-                       return new URI(PREFIX + base64Str);
-               } catch (URISyntaxException e) {
-                       throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
-               }
-
-       }
-
-       public static byte[] bytesFromDataURI(URI uri) {
-               if (!"data".equals(uri.getScheme()))
-                       throw new IllegalArgumentException("URI must have 'data' as a scheme");
-               String schemeSpecificPart = uri.getSchemeSpecificPart();
-               int commaIndex = schemeSpecificPart.indexOf(',');
-               String prefix = schemeSpecificPart.substring(0, commaIndex);
-               List<String> info = Arrays.asList(prefix.split(";"));
-               if (!info.contains("base64"))
-                       throw new IllegalArgumentException("URI must specify base64");
-
-               String base64Str = schemeSpecificPart.substring(commaIndex + 1);
-               return Base64.getDecoder().decode(base64Str);
-
-       }
-
-       public static <T> boolean isString(T t) {
-               return t instanceof String;
-       }
-
-       /** Singleton. */
-       private ContentUtils() {
-
-       }
-}
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 (file)
index 1e34456..0000000
+++ /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
- * <code>String<code>.
- */
-public enum CrAttributeType implements ContentNameSupplier {
-       BOOLEAN(Boolean.class, new BooleanFormatter()), //
-       INTEGER(Integer.class, new IntegerFormatter()), //
-       LONG(Long.class, new LongFormatter()), //
-       DOUBLE(Double.class, new DoubleFormatter()), //
-       // we do not support short and float, like recent additions to Java
-       // (e.g. optional primitives)
-       DATE_TIME(Instant.class, new InstantFormatter()), //
-       UUID(UUID.class, new UuidFormatter()), //
-       ANY_URI(URI.class, new UriFormatter()), //
-       STRING(String.class, new StringFormatter()), //
-       ;
-
-       private final Class<?> clss;
-       private final AttributeFormatter<?> formatter;
-
-       private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
-               this.clss = clss;
-               this.formatter = formatter;
-       }
-
-       public Class<?> getClss() {
-               return clss;
-       }
-
-       public AttributeFormatter<?> getFormatter() {
-               return formatter;
-       }
-
-       @Override
-       public String getDefaultPrefix() {
-               if (equals(UUID))
-                       return CrName.CR_DEFAULT_PREFIX;
-               else
-                       return "xs";
-       }
-
-       @Override
-       public String getNamespaceURI() {
-               if (equals(UUID))
-                       return CrName.CR_NAMESPACE_URI;
-               else
-                       return XMLConstants.W3C_XML_SCHEMA_NS_URI;
-       }
-
-       public static Object parse(String str) {
-               if (str == null)
-                       throw new IllegalArgumentException("String cannot be null");
-               // order IS important
-               try {
-                       if (str.length() == 4 || str.length() == 5)
-                               return BOOLEAN.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return INTEGER.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return LONG.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return DOUBLE.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       return DATE_TIME.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       if (str.length() == 36)
-                               return UUID.getFormatter().parse(str);
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-               try {
-                       java.net.URI uri = (java.net.URI) ANY_URI.getFormatter().parse(str);
-                       if (uri.getScheme() != null)
-                               return uri;
-                       String path = uri.getPath();
-                       if (path.indexOf('/') >= 0)
-                               return uri;
-                       // if it is not clearly a path, we will consider it as a string
-                       // because their is no way to distinguish between 'any_string'
-                       // and 'any_file_name'.
-                       // Note that providing ./any_file_name would result in an equivalent URI
-               } catch (IllegalArgumentException e) {
-                       // silent
-               }
-
-               // default
-               return STRING.getFormatter().parse(str);
-       }
-
-       static class BooleanFormatter implements AttributeFormatter<Boolean> {
-
-               /**
-                * @param str must be exactly equals to either 'true' or 'false' (different
-                *            contract than {@link Boolean#parseBoolean(String)}.
-                */
-               @Override
-               public Boolean parse(String str) throws IllegalArgumentException {
-                       if ("true".equals(str))
-                               return Boolean.TRUE;
-                       if ("false".equals(str))
-                               return Boolean.FALSE;
-                       throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
-               }
-       }
-
-       static class IntegerFormatter implements AttributeFormatter<Integer> {
-               @Override
-               public Integer parse(String str) throws NumberFormatException {
-                       return Integer.parseInt(str);
-               }
-       }
-
-       static class LongFormatter implements AttributeFormatter<Long> {
-               @Override
-               public Long parse(String str) throws NumberFormatException {
-                       return Long.parseLong(str);
-               }
-       }
-
-       static class DoubleFormatter implements AttributeFormatter<Double> {
-
-               @Override
-               public Double parse(String str) throws NumberFormatException {
-                       return Double.parseDouble(str);
-               }
-       }
-
-       static class InstantFormatter implements AttributeFormatter<Instant> {
-
-               @Override
-               public Instant parse(String str) throws IllegalArgumentException {
-                       try {
-                               return Instant.parse(str);
-                       } catch (DateTimeParseException e) {
-                               throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
-                       }
-               }
-       }
-
-       static class UuidFormatter implements AttributeFormatter<UUID> {
-
-               @Override
-               public UUID parse(String str) throws IllegalArgumentException {
-                       return java.util.UUID.fromString(str);
-               }
-       }
-
-       static class UriFormatter implements AttributeFormatter<URI> {
-
-               @Override
-               public URI parse(String str) throws IllegalArgumentException {
-                       try {
-                               return new URI(str);
-                       } catch (URISyntaxException e) {
-                               throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
-                       }
-               }
-
-       }
-
-       static class StringFormatter implements AttributeFormatter<String> {
-
-               @Override
-               public String parse(String str) {
-                       return str;
-               }
-
-               @Override
-               public String format(String obj) {
-                       return obj;
-               }
-
-       }
-
-}
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 (file)
index 283e5cc..0000000
+++ /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 (file)
index c83fe38..0000000
+++ /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<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
-       private final FS fs;
-       /** null for non absolute paths */
-       private final ST fileStore;
-
-       private final String[] segments;// null means root
-       private final boolean absolute;
-
-       private final String separator;
-
-       // optim
-       private final int hashCode;
-
-       public AbstractFsPath(FS filesSystem, String path) {
-               if (path == null)
-                       throw new IllegalArgumentException("Path cannot be null");
-               this.fs = filesSystem;
-               this.separator = fs.getSeparator();
-               // TODO deal with both path and separator being empty strings
-               if (path.equals(separator)) {// root
-                       this.segments = null;
-                       this.absolute = true;
-                       this.hashCode = 0;
-                       this.fileStore = fs.getBaseFileStore();
-                       return;
-               } else if (path.equals("")) {// empty path
-                       this.segments = new String[] { "" };
-                       this.absolute = false;
-                       this.hashCode = "".hashCode();
-                       this.fileStore = null;
-                       return;
-               }
-
-               this.absolute = path.startsWith(toStringRoot());
-
-               String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
-                               path.endsWith(separator) ? path.length() - separator.length() : path.length());
-               this.segments = trimmedPath.split(separator);
-               // clean up
-               for (int i = 0; i < this.segments.length; i++) {
-                       this.segments[i] = cleanUpSegment(this.segments[i]);
-               }
-               this.hashCode = this.segments[this.segments.length - 1].hashCode();
-
-               this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
-       }
-
-       protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
-               this.segments = segments;
-               this.absolute = absolute;
-               this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
-               this.separator = filesSystem.getSeparator();
-//             super(path, path == null ? true : absolute, filesSystem.getSeparator());
-//             assert path == null ? absolute == true : true;
-               this.fs = filesSystem;
-//             this.path = path;
-//             this.absolute = path == null ? true : absolute;
-               if (isAbsolute() && fileStore == null)
-                       throw new IllegalArgumentException("Absolute path requires a file store");
-               if (!isAbsolute() && fileStore != null)
-                       throw new IllegalArgumentException("A file store should not be provided for a relative path");
-               this.fileStore = fileStore;
-               assert !(absolute && fileStore == null);
-       }
-
-       protected Path retrieve(String path) {
-               return getFileSystem().getPath(path);
-       }
-
-       @Override
-       public FS getFileSystem() {
-               return fs;
-       }
-
-       public ST getFileStore() {
-               return fileStore;
-       }
-
-       @Override
-       public boolean isAbsolute() {
-               return absolute;
-       }
-
-       @Override
-       public URI toUri() {
-               try {
-                       return new URI(fs.provider().getScheme(), toString(), null);
-               } catch (URISyntaxException e) {
-                       throw new IllegalStateException("Cannot create URI for " + toString(), e);
-               }
-       }
-
-       @Override
-       public Path toAbsolutePath() {
-               if (isAbsolute())
-                       return this;
-               // FIXME it doesn't seem right
-               return newInstance(getSegments(), true);
-       }
-
-       @Override
-       public Path toRealPath(LinkOption... options) throws IOException {
-               return this;
-       }
-
-       @Override
-       public File toFile() {
-               throw new UnsupportedOperationException();
-       }
-
-       /*
-        * PATH OPERATIONS
-        */
-       public final Path resolveSibling(Path other) {
-               if (other == null)
-                       throw new NullPointerException();
-               Path parent = getParent();
-               return (parent == null) ? other : parent.resolve(other);
-       }
-
-       @Override
-       public final Path resolveSibling(String other) {
-               return resolveSibling(getFileSystem().getPath(other));
-       }
-
-       public final Path resolve(String other) {
-               return resolve(retrieve(other));
-       }
-
-       public boolean startsWith(Path other) {
-               return toString().startsWith(other.toString());
-       }
-
-       public boolean endsWith(Path other) {
-               return toString().endsWith(other.toString());
-       }
-
-       @Override
-       public Path normalize() {
-               // always normalized
-               return this;
-       }
-
-       @Override
-       public final Iterator<Path> iterator() {
-               return new Iterator<Path>() {
-                       private int i = 0;
-
-                       @Override
-                       public boolean hasNext() {
-                               return (i < getNameCount());
-                       }
-
-                       @Override
-                       public Path next() {
-                               if (i < getNameCount()) {
-                                       Path result = getName(i);
-                                       i++;
-                                       return result;
-                               } else {
-                                       throw new NoSuchElementException();
-                               }
-                       }
-
-                       @Override
-                       public void remove() {
-                               throw new UnsupportedOperationException();
-                       }
-               };
-       }
-
-       @Override
-       public int compareTo(Path other) {
-               return toString().compareTo(other.toString());
-       }
-
-       public Path resolve(Path other) {
-               AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
-               if (otherPath.isAbsolute())
-                       return other;
-               String[] newPath;
-               if (isRoot()) {
-                       newPath = new String[otherPath.segments.length];
-                       System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
-               } else {
-                       newPath = new String[segments.length + otherPath.segments.length];
-                       System.arraycopy(segments, 0, newPath, 0, segments.length);
-                       System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
-               }
-               if (!absolute)
-                       return newInstance(newPath, absolute);
-               else {
-                       return newInstance(toString(newPath));
-               }
-       }
-
-       public Path relativize(Path other) {
-               if (equals(other))
-                       return newInstance("");
-               if (other.toString().startsWith(this.toString())) {
-                       String p1 = toString();
-                       String p2 = other.toString();
-                       String relative = p2.substring(p1.length(), p2.length());
-                       if (relative.charAt(0) == '/')
-                               relative = relative.substring(1);
-                       return newInstance(relative);
-               }
-               throw new IllegalArgumentException(other + " cannot be relativized against " + this);
-       }
-
-       /*
-        * FACTORIES
-        */
-       protected abstract AbstractFsPath<FS, ST> newInstance(String path);
-
-       protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
-
-       /*
-        * CUSTOMISATIONS
-        */
-       protected String toStringRoot() {
-               return separator;
-       }
-
-       protected String cleanUpSegment(String segment) {
-               return segment;
-       }
-
-       protected boolean isRoot() {
-               return segments == null;
-       }
-
-       protected boolean isEmpty() {
-               return segments.length == 1 && "".equals(segments[0]);
-       }
-
-       /*
-        * PATH OPERATIONS
-        */
-       public AbstractFsPath<FS, ST> getRoot() {
-               return newInstance(toStringRoot());
-       }
-
-       public AbstractFsPath<FS, ST> getParent() {
-               if (isRoot())
-                       return null;
-               // FIXME empty path?
-               if (segments.length == 1)// first level
-                       return newInstance(toStringRoot());
-               String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
-               if (!absolute)
-                       return newInstance(parentPath, absolute);
-               else
-                       return newInstance(toString(parentPath));
-       }
-
-       public AbstractFsPath<FS, ST> getFileName() {
-               if (isRoot())
-                       return null;
-               return newInstance(segments[segments.length - 1]);
-       }
-
-       public int getNameCount() {
-               if (isRoot())
-                       return 0;
-               return segments.length;
-       }
-
-       public AbstractFsPath<FS, ST> getName(int index) {
-               if (isRoot())
-                       return null;
-               return newInstance(segments[index]);
-       }
-
-       public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
-               if (isRoot())
-                       return null;
-               String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
-               return newInstance(parentPath, false);
-       }
-
-       public boolean startsWith(String other) {
-               return toString().startsWith(other);
-       }
-
-       public boolean endsWith(String other) {
-               return toString().endsWith(other);
-       }
-
-       /*
-        * UTILITIES
-        */
-       protected String toString(String[] path) {
-               if (isRoot())
-                       return toStringRoot();
-               StringBuilder sb = new StringBuilder();
-               if (isAbsolute())
-                       sb.append(separator);
-               for (int i = 0; i < path.length; i++) {
-                       if (i != 0)
-                               sb.append(separator);
-                       sb.append(path[i]);
-               }
-               return sb.toString();
-       }
-
-       @Override
-       public String toString() {
-               return toString(segments);
-       }
-
-       @Override
-       public int hashCode() {
-               return hashCode;
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof AbstractFsPath))
-                       return false;
-               AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
-
-               if (isRoot()) {// root
-                       if (other.isRoot())// root
-                               return true;
-                       else
-                               return false;
-               } else {
-                       if (other.isRoot())// root
-                               return false;
-               }
-               // non root
-               if (segments.length != other.segments.length)
-                       return false;
-               for (int i = 0; i < segments.length; i++) {
-                       if (!segments[i].equals(other.segments[i]))
-                               return false;
-               }
-               return true;
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               return newInstance(toString());
-       }
-
-       /*
-        * GETTERS / SETTERS
-        */
-       protected String[] getSegments() {
-               return segments;
-       }
-
-       protected String getSeparator() {
-               return separator;
-       }
-
-       /*
-        * UNSUPPORTED
-        */
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
-               throw new UnsupportedOperationException();
-       }
-
-}
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 (file)
index 0088734..0000000
+++ /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 (file)
index 36369ca..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.argeo.api.gcr.fs;
-
-import java.nio.file.FileSystem;
-
-public abstract class AbstractFsSystem<ST extends AbstractFsStore> extends FileSystem {
-       public abstract ST getBaseFileStore();
-
-       public abstract ST getFileStore(String path);
-}
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 (file)
index 2d3bcde..0000000
+++ /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<QName, Object> implements Content {
-
-       /*
-        * ATTRIBUTES OPERATIONS
-        */
-       protected abstract Iterable<QName> keys();
-
-       protected abstract void removeAttr(QName key);
-
-       @Override
-       public Set<Entry<QName, Object>> entrySet() {
-               Set<Entry<QName, Object>> result = new AttrSet();
-               return result;
-       }
-
-       @Override
-       public Class<?> getType(QName key) {
-               return String.class;
-       }
-
-       @Override
-       public boolean isMultiple(QName key) {
-               return false;
-       }
-
-       @Override
-       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
-               Object value = get(key);
-               if (value == null)
-                       return null;
-               if (value instanceof List) {
-                       try {
-                               List<A> res = (List<A>) value;
-                               return Optional.of(res);
-                       } catch (ClassCastException e) {
-                               List<A> res = new ArrayList<>();
-                               List<?> lst = (List<?>) value;
-                               try {
-                                       for (Object o : lst) {
-                                               A item = (A) o;
-                                               res.add(item);
-                                       }
-                                       return Optional.of(res);
-                               } catch (ClassCastException e1) {
-                                       return Optional.empty();
-                               }
-                       }
-               } else {// singleton
-                       try {
-                               A res = (A) value;
-                               return Optional.of(Collections.singletonList(res));
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
-                       }
-               }
-       }
-
-       /*
-        * CONTENT OPERATIONS
-        */
-
-       @Override
-       public String getPath() {
-               List<Content> ancestors = new ArrayList<>();
-               collectAncestors(ancestors, this);
-               StringBuilder path = new StringBuilder();
-               for (Content c : ancestors) {
-                       QName name = c.getName();
-                       // FIXME
-                       if (!CrName.ROOT.get().equals(name))
-                               path.append('/').append(name);
-               }
-               return path.toString();
-       }
-
-       private void collectAncestors(List<Content> ancestors, Content content) {
-               if (content == null)
-                       return;
-               ancestors.add(0, content);
-               collectAncestors(ancestors, content.getParent());
-       }
-
-       /*
-        * UTILITIES
-        */
-       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
-               // check whether clss is Object.class
-               return clss.isAssignableFrom(Object.class);
-       }
-
-       @Override
-       public String toString() {
-               return "content " + getPath();
-       }
-
-       /*
-        * SUB CLASSES
-        */
-
-       class AttrSet extends AbstractSet<Entry<QName, Object>> {
-
-               @Override
-               public Iterator<Entry<QName, Object>> iterator() {
-                       final Iterator<QName> keys = keys().iterator();
-                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
-
-                               QName key = null;
-
-                               @Override
-                               public boolean hasNext() {
-                                       return keys.hasNext();
-                               }
-
-                               @Override
-                               public Entry<QName, Object> next() {
-                                       key = keys.next();
-                                       // TODO check type
-                                       Optional<?> value = get(key, Object.class);
-                                       assert !value.isEmpty();
-                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
-                                       return entry;
-                               }
-
-                               @Override
-                               public void remove() {
-                                       if (key != null) {
-                                               AbstractContent.this.removeAttr(key);
-                                       } else {
-                                               throw new IllegalStateException("Iteration has not started");
-                                       }
-                               }
-
-                       };
-                       return it;
-               }
-
-               @Override
-               public int size() {
-                       int count = 0;
-                       for (QName key : keys()) {
-                               count++;
-                       }
-                       return count;
-               }
-
-       }
-}
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 (file)
index 3a24ca2..0000000
+++ /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 (file)
index e75f62d..0000000
+++ /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 (file)
index 2db13f9..0000000
+++ /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 (file)
index 4e93684..0000000
+++ /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<String> findPrefixes(String namespaceURI);
-
-       /** To be overridden for optimisation, as it will be called a lot */
-       default String findPrefix(String namespaceURI) {
-               Set<String> prefixes = findPrefixes(namespaceURI);
-               if (prefixes.isEmpty())
-                       return null;
-               return prefixes.iterator().next();
-       }
-
-       @Override
-       default String getNamespaceURI(String prefix) {
-               String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix);
-               if (namespaceURI != null)
-                       return namespaceURI;
-               if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
-                       return XMLConstants.NULL_NS_URI;
-               namespaceURI = findNamespace(prefix);
-               if (namespaceURI != null)
-                       return namespaceURI;
-               return XMLConstants.NULL_NS_URI;
-       }
-
-       @Override
-       default String getPrefix(String namespaceURI) {
-               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
-               if (prefix != null)
-                       return prefix;
-               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
-                       return XMLConstants.DEFAULT_NS_PREFIX;
-               return findPrefix(namespaceURI);
-       }
-
-       @Override
-       default Iterator<String> getPrefixes(String namespaceURI) {
-               Iterator<String> standard = ContentNameSupplier.getStandardPrefixes(namespaceURI);
-               if (standard != null)
-                       return standard;
-               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
-                       return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
-               Set<String> prefixes = findPrefixes(namespaceURI);
-               assert prefixes != null;
-               return prefixes.iterator();
-       }
-
-}
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 (file)
index 0000000..04c5d2d
--- /dev/null
@@ -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 <A> Optional<A> get(QName key, Class<A> clss) {
+               if (isDefaultAttrTypeRequested(clss)) {
+                       return Optional.of((A) get(jcrNode, key.toString()));
+               }
+               return Optional.of((A) Jcr.get(jcrNode, key.toString()));
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               try {
+                       return new JcrContentIterator(jcrNode.getNodes());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot list children of " + jcrNode, e);
+               }
+       }
+
+       @Override
+       protected Iterable<QName> keys() {
+               return new Iterable<QName>() {
+
+                       @Override
+                       public Iterator<QName> iterator() {
+                               try {
+                                       PropertyIterator propertyIterator = jcrNode.getProperties();
+                                       return new JcrKeyIterator(provider, propertyIterator);
+                               } catch (RepositoryException e) {
+                                       throw new JcrException("Cannot retrive properties from " + jcrNode, e);
+                               }
+                       }
+               };
+       }
+
+       public Node getJcrNode() {
+               return jcrNode;
+       }
+
+       /** Cast to a standard Java object. */
+       static Object get(Node node, String property) {
+               try {
+                       Value value = node.getProperty(property).getValue();
+                       switch (value.getType()) {
+                       case PropertyType.STRING:
+                               return value.getString();
+                       case PropertyType.DOUBLE:
+                               return (Double) value.getDouble();
+                       case PropertyType.LONG:
+                               return (Long) value.getLong();
+                       case PropertyType.BOOLEAN:
+                               return (Boolean) value.getBoolean();
+                       case PropertyType.DATE:
+                               Calendar calendar = value.getDate();
+                               return calendar.toInstant();
+                       case PropertyType.BINARY:
+                               throw new IllegalArgumentException("Binary is not supported as an attribute");
+                       default:
+                               return value.getString();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
+               }
+       }
+
+       class JcrContentIterator implements Iterator<Content> {
+               private final NodeIterator nodeIterator;
+               // we keep track in order to be able to delete it
+               private JcrContent current = null;
+
+               protected JcrContentIterator(NodeIterator nodeIterator) {
+                       this.nodeIterator = nodeIterator;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return nodeIterator.hasNext();
+               }
+
+               @Override
+               public Content next() {
+                       current = new JcrContent(session, provider, nodeIterator.nextNode());
+                       return current;
+               }
+
+               @Override
+               public void remove() {
+                       if (current != null) {
+                               Jcr.remove(current.getJcrNode());
+                       }
+               }
+
+       }
+
+       @Override
+       public Content getParent() {
+               return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               if (classes.length > 0) {
+                       QName primaryType = classes[0];
+                       Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
+                       for (int i = 1; i < classes.length; i++) {
+                               try {
+                                       child.addMixin(classes[i].toString());
+                               } catch (RepositoryException e) {
+                                       throw new JcrException("Cannot add child to " + getJcrNode(), e);
+                               }
+                       }
+
+               } else {
+                       Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
+               }
+               return null;
+       }
+
+       @Override
+       public void remove() {
+               Jcr.remove(getJcrNode());
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               Property property = Jcr.getProperty(getJcrNode(), key.toString());
+               if (property != null) {
+                       try {
+                               property.remove();
+                       } catch (RepositoryException e) {
+                               throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
+                       }
+               }
+
+       }
+
+       class JcrKeyIterator implements Iterator<QName> {
+               private final JcrContentProvider contentSession;
+               private final PropertyIterator propertyIterator;
+
+               protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
+                       this.contentSession = contentSession;
+                       this.propertyIterator = propertyIterator;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return propertyIterator.hasNext();
+               }
+
+               @Override
+               public QName next() {
+                       Property property = null;
+                       try {
+                               property = propertyIterator.nextProperty();
+                               // TODO map standard property names
+                               return session.parsePrefixedName(property.getName());
+                       } catch (RepositoryException e) {
+                               throw new JcrException("Cannot retrieve property " + property, null);
+                       }
+               }
+
+       }
+}
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 (file)
index 0000000..ef8e375
--- /dev/null
@@ -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<String> 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 (file)
index af63ead..0000000
+++ /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 <A> Optional<A> get(QName key, Class<A> clss) {
-               if (isDefaultAttrTypeRequested(clss)) {
-                       return Optional.of((A) get(jcrNode, key.toString()));
-               }
-               return Optional.of((A) Jcr.get(jcrNode, key.toString()));
-       }
-
-       @Override
-       public Iterator<Content> iterator() {
-               try {
-                       return new JcrContentIterator(jcrNode.getNodes());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot list children of " + jcrNode, e);
-               }
-       }
-
-       @Override
-       protected Iterable<QName> keys() {
-               return new Iterable<QName>() {
-
-                       @Override
-                       public Iterator<QName> iterator() {
-                               try {
-                                       PropertyIterator propertyIterator = jcrNode.getProperties();
-                                       return new JcrKeyIterator(provider, propertyIterator);
-                               } catch (RepositoryException e) {
-                                       throw new JcrException("Cannot retrive properties from " + jcrNode, e);
-                               }
-                       }
-               };
-       }
-
-       public Node getJcrNode() {
-               return jcrNode;
-       }
-
-       /** Cast to a standard Java object. */
-       static Object get(Node node, String property) {
-               try {
-                       Value value = node.getProperty(property).getValue();
-                       switch (value.getType()) {
-                       case PropertyType.STRING:
-                               return value.getString();
-                       case PropertyType.DOUBLE:
-                               return (Double) value.getDouble();
-                       case PropertyType.LONG:
-                               return (Long) value.getLong();
-                       case PropertyType.BOOLEAN:
-                               return (Boolean) value.getBoolean();
-                       case PropertyType.DATE:
-                               Calendar calendar = value.getDate();
-                               return calendar.toInstant();
-                       case PropertyType.BINARY:
-                               throw new IllegalArgumentException("Binary is not supported as an attribute");
-                       default:
-                               return value.getString();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
-               }
-       }
-
-       class JcrContentIterator implements Iterator<Content> {
-               private final NodeIterator nodeIterator;
-               // we keep track in order to be able to delete it
-               private JcrContent current = null;
-
-               protected JcrContentIterator(NodeIterator nodeIterator) {
-                       this.nodeIterator = nodeIterator;
-               }
-
-               @Override
-               public boolean hasNext() {
-                       return nodeIterator.hasNext();
-               }
-
-               @Override
-               public Content next() {
-                       current = new JcrContent(session, provider, nodeIterator.nextNode());
-                       return current;
-               }
-
-               @Override
-               public void remove() {
-                       if (current != null) {
-                               Jcr.remove(current.getJcrNode());
-                       }
-               }
-
-       }
-
-       @Override
-       public Content getParent() {
-               return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
-       }
-
-       @Override
-       public Content add(QName name, QName... classes) {
-               if (classes.length > 0) {
-                       QName primaryType = classes[0];
-                       Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
-                       for (int i = 1; i < classes.length; i++) {
-                               try {
-                                       child.addMixin(classes[i].toString());
-                               } catch (RepositoryException e) {
-                                       throw new JcrException("Cannot add child to " + getJcrNode(), e);
-                               }
-                       }
-
-               } else {
-                       Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
-               }
-               return null;
-       }
-
-       @Override
-       public void remove() {
-               Jcr.remove(getJcrNode());
-       }
-
-       @Override
-       protected void removeAttr(QName key) {
-               Property property = Jcr.getProperty(getJcrNode(), key.toString());
-               if (property != null) {
-                       try {
-                               property.remove();
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
-                       }
-               }
-
-       }
-
-       class JcrKeyIterator implements Iterator<QName> {
-               private final JcrContentProvider contentSession;
-               private final PropertyIterator propertyIterator;
-
-               protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
-                       this.contentSession = contentSession;
-                       this.propertyIterator = propertyIterator;
-               }
-
-               @Override
-               public boolean hasNext() {
-                       return propertyIterator.hasNext();
-               }
-
-               @Override
-               public QName next() {
-                       Property property = null;
-                       try {
-                               property = propertyIterator.nextProperty();
-                               // TODO map standard property names
-                               return session.parsePrefixedName(property.getName());
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot retrieve property " + property, null);
-                       }
-               }
-
-       }
-}
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 (file)
index 761c087..0000000
+++ /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<String> getPrefixes(String namespaceURI) {
-               try {
-                       return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-}
index 3d0fcfe668b9a422bdeb528cde573d6dcc608077..4b329810fa0cd338cee2b201a91b7f7d83e06eef 100644 (file)
@@ -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;
 
index 8782be9eed5b5659b52ea8aa806205a1bb3722dc..7318b70962aea789b61091be99b0f8f73428dfb5 100644 (file)
@@ -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<JcrFileSystem, WorkspaceFileStore> {
index e8f24c9de8b44ebe8ba68500c314469ff52593f5..ce4205a974ac77b66a1cfb58ac1565864217d03c 100644 (file)
@@ -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}. */
index ba6d7b477eca446a2e5b9625704e01fda5e6b9a0..4039f2baad71fa232a2a1ea25ac56f6f5d231add 100644 (file)
@@ -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;
index 63b29c9638bcd5753a63e9bee34f236af3119bf5..8109c40ac07e4a702df49751fcf094533d538d36 100644 (file)
@@ -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;
 
index 2bc5b03d1209d47fb7451a7a746fc84c7a86d581..7a394495a3699bbd970d58d867cf8e21f0734b13 100644 (file)
        <artifactId>org.argeo.cms.tp</artifactId>
        <packaging>jar</packaging>
        <name>CMS Third Parties Adapters</name>
-       <description>Workarounds or trivial implementations from some third parties, typically logging</description>
+       <description>Workarounds or trivial implementations of some third parties, typically logging</description>
        <dependencies>
                <dependency>
                        <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.api</artifactId>
+                       <artifactId>org.argeo.api.cms</artifactId>
                        <version>2.3-SNAPSHOT</version>
                </dependency>
        </dependencies>
index 237af520b28dd03cf4e6e55af70d4af516d1948a..38112fc3b234e0ab73714d4354b5dde0a613906b 100644 (file)
        <dependencies>
                <dependency>
                        <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.api</artifactId>
+                       <artifactId>org.argeo.api.cms</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.api.acr</artifactId>
                        <version>2.3-SNAPSHOT</version>
                </dependency>
                <dependency>
                        <artifactId>org.argeo.util</artifactId>
                        <version>2.3-SNAPSHOT</version>
                </dependency>
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.commons</groupId> -->
-<!--                   <artifactId>org.argeo.core</artifactId> -->
-<!--                   <version>2.3-SNAPSHOT</version> -->
-<!--           </dependency> -->
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.commons</groupId> -->
-<!--                   <artifactId>org.argeo.maintenance</artifactId> -->
-<!--                   <version>2.3-SNAPSHOT</version> -->
-<!--           </dependency> -->
        </dependencies>
 </project>
\ No newline at end of file
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 (file)
index 0000000..9cd8bd2
--- /dev/null
@@ -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<String, ContentProvider> partitions = new TreeMap<>();
+
+       // TODO synchronize ?
+       private NavigableMap<String, String> prefixes = new TreeMap<>();
+
+       public CmsContentRepository() {
+               prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
+               prefixes.put("basic", CrName.CR_NAMESPACE_URI);
+               prefixes.put("owner", CrName.CR_NAMESPACE_URI);
+               prefixes.put("posix", CrName.CR_NAMESPACE_URI);
+       }
+
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+
+       /*
+        * REPOSITORY
+        */
+
+       @Override
+       public ContentSession get() {
+               return get(CmsContextImpl.getCmsContext().getDefaultLocale());
+       }
+
+       @Override
+       public ContentSession get(Locale locale) {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               return new CmsContentSession(subject, locale);
+       }
+
+       public void addProvider(String base, ContentProvider provider) {
+               partitions.put(base, provider);
+       }
+
+       public void registerPrefix(String prefix, String namespaceURI) {
+               String registeredUri = prefixes.get(prefix);
+               if (registeredUri == null) {
+                       prefixes.put(prefix, namespaceURI);
+                       return;
+               }
+               if (!registeredUri.equals(namespaceURI))
+                       throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
+               // do nothing if same namespace is already registered
+       }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       /*
+        * SESSION
+        */
+
+       class CmsContentSession implements ProvidedSession {
+               private Subject subject;
+               private Locale locale;
+
+               public CmsContentSession(Subject subject, Locale locale) {
+                       this.subject = subject;
+                       this.locale = locale;
+               }
+
+               @Override
+               public Content get(String path) {
+                       Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
+                       String mountPath = entry.getKey();
+                       ContentProvider provider = entry.getValue();
+                       String relativePath = path.substring(mountPath.length());
+                       return provider.get(CmsContentSession.this, mountPath, relativePath);
+               }
+
+               @Override
+               public Subject getSubject() {
+                       return subject;
+               }
+
+               @Override
+               public Locale getLocale() {
+                       return locale;
+               }
+
+               @Override
+               public ProvidedRepository getRepository() {
+                       return CmsContentRepository.this;
+               }
+
+               /*
+                * NAMESPACE CONTEXT
+                */
+
+               @Override
+               public String findNamespace(String prefix) {
+                       return prefixes.get(prefix);
+               }
+
+               @Override
+               public Set<String> findPrefixes(String namespaceURI) {
+                       Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
+                                       .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
+
+                       return res;
+               }
+
+               @Override
+               public String findPrefix(String namespaceURI) {
+                       if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
+                               return CrName.CR_DEFAULT_PREFIX;
+                       return ProvidedSession.super.findPrefix(namespaceURI);
+               }
+
+       }
+
+}
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 (file)
index 0000000..bfcd011
--- /dev/null
@@ -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<QName, String> BASIC_KEYS;
+       private static final Map<QName, String> POSIX_KEYS;
+       static {
+               BASIC_KEYS = new HashMap<>();
+               BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
+               BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
+               BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
+               BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+
+               POSIX_KEYS = new HashMap<>(BASIC_KEYS);
+               POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
+               POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
+               POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
+       }
+
+       private final ProvidedSession session;
+       private final FsContentProvider provider;
+       private final Path path;
+       private final boolean isRoot;
+       private final QName name;
+
+       protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
+               this.session = session;
+               this.provider = contentProvider;
+               this.path = path;
+               this.isRoot = contentProvider.isRoot(path);
+               // TODO check file names with ':' ?
+               if (isRoot)
+                       this.name = CrName.ROOT.get();
+               else
+                       this.name = session.parsePrefixedName(path.getFileName().toString());
+       }
+
+       protected FsContent(FsContent context, Path path) {
+               this(context.getSession(), context.getProvider(), path);
+       }
+
+       private boolean isPosix() {
+               return path.getFileSystem().supportedFileAttributeViews().contains("posix");
+       }
+
+       @Override
+       public QName getName() {
+               return name;
+       }
+
+       /*
+        * ATTRIBUTES
+        */
+
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               Object value;
+               try {
+                       // We need to add user: when accessing via Files#getAttribute
+                       value = Files.getAttribute(path, toFsAttributeKey(key));
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
+               }
+               A res = null;
+               if (value instanceof FileTime) {
+                       if (clss.isAssignableFrom(FileTime.class))
+                               res = (A) value;
+                       Instant instant = ((FileTime) value).toInstant();
+                       if (Object.class.isAssignableFrom(clss)) {// plain object requested
+                               res = (A) instant;
+                       }
+                       // TODO perform trivial file conversion to other formats
+               }
+               if (value instanceof byte[]) {
+                       res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
+               }
+               if (res == null)
+                       try {
+                               res = (A) value;
+                       } catch (ClassCastException e) {
+                               return Optional.empty();
+                       }
+               return Optional.of(res);
+       }
+
+       @Override
+       protected Iterable<QName> keys() {
+               Set<QName> result = new HashSet<>(isPosix() ? POSIX_KEYS.keySet() : BASIC_KEYS.keySet());
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               if (udfav != null) {
+                       try {
+                               for (String name : udfav.list()) {
+                                       result.add(session.parsePrefixedName(name));
+                               }
+                       } catch (IOException e) {
+                               throw new ContentResourceException("Cannot list attributes for " + path, e);
+                       }
+               }
+               return result;
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               try {
+                       udfav.delete(session.toPrefixedName(key));
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+               }
+       }
+
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+               try {
+                       int size = udfav.write(session.toPrefixedName(key), bb);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+               }
+               return previous;
+       }
+
+       protected String toFsAttributeKey(QName key) {
+               if (POSIX_KEYS.containsKey(key))
+                       return POSIX_KEYS.get(key);
+               else
+                       return USER_ + session.toPrefixedName(key);
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+       @Override
+       public Iterator<Content> iterator() {
+               if (Files.isDirectory(path)) {
+                       try {
+                               return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
+                       } catch (IOException e) {
+                               throw new ContentResourceException("Cannot list " + path, e);
+                       }
+               } else {
+                       return Collections.emptyIterator();
+               }
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               try {
+                       Path newPath = path.resolve(session.toPrefixedName(name));
+                       if (ContentName.contains(classes, CrName.COLLECTION.get()))
+                               Files.createDirectory(newPath);
+                       else
+                               Files.createFile(newPath);
+
+//             for(ContentClass clss:classes) {
+//                     Files.setAttribute(newPath, name, newPath, null)
+//             }
+                       return new FsContent(this, newPath);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot create new content", e);
+               }
+       }
+
+       @Override
+       public void remove() {
+               FsUtils.delete(path);
+       }
+
+       @Override
+       public Content getParent() {
+               if (isRoot)
+                       return null;// TODO deal with mounts
+               return new FsContent(this, path.getParent());
+       }
+
+       /*
+        * ACCESSORS
+        */
+       @Override
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       @Override
+       public FsContentProvider getProvider() {
+               return provider;
+       }
+
+}
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 (file)
index 0000000..99ed3a8
--- /dev/null
@@ -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 (file)
index 0000000..626f582
--- /dev/null
@@ -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<QName> keys() {
+               // TODO implement an iterator?
+               Set<QName> result = new HashSet<>();
+               NamedNodeMap attributes = element.getAttributes();
+               for (int i = 0; i < attributes.getLength(); i++) {
+                       Attr attr = (Attr) attributes.item(i);
+                       QName key = toQName(attr);
+                       result.add(key);
+               }
+               return result;
+       }
+
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               if (element.hasAttributeNS(namespaceUriOrNull, key.getLocalPart())) {
+                       String value = element.getAttributeNS(namespaceUriOrNull, key.getLocalPart());
+                       if (clss.isAssignableFrom(String.class))
+                               return Optional.of((A) value);
+                       else
+                               return Optional.empty();
+               } else
+                       return null;
+       }
+
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               element.setAttributeNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+                               value.toString());
+               return previous;
+       }
+       
+       
+
+       @Override
+       public boolean hasText() {
+//             return element instanceof Text;
+               if (hasText != null)
+                       return hasText;
+               NodeList nodeList = element.getChildNodes();
+               if (nodeList.getLength() > 1) {
+                       hasText = false;
+                       return hasText;
+               }
+               nodes: for (int i = 0; i < nodeList.getLength(); i++) {
+                       Node node = nodeList.item(i);
+                       if (node instanceof Text) {
+                               Text text = (Text) node;
+                               if (!text.isElementContentWhitespace()) {
+                                       hasText = true;
+                                       break nodes;
+                               }
+                       }
+               }
+               if (hasText == null)
+                       hasText = false;
+               return hasText;
+//             if (text != null)
+//                     return true;
+//             text = element.getTextContent();
+//             return text != null;
+       }
+
+       @Override
+       public String getText() {
+               if (hasText())
+                       return element.getTextContent();
+               else
+                       return null;
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public Iterator<Content> iterator() {
+               NodeList nodeList = element.getChildNodes();
+               return new ElementIterator(session, provider, nodeList);
+       }
+
+       @Override
+       public Content getParent() {
+               Node parent = element.getParentNode();
+               if (parent == null)
+                       return null;
+               if (!(parent instanceof Element))
+                       throw new IllegalStateException("Parent is not an element");
+               return new DomContent(this, (Element) parent);
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               // TODO consider classes
+               Document document = this.element.getOwnerDocument();
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+                               : name.getNamespaceURI();
+               Element child = document.createElementNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+               element.appendChild(child);
+               return new DomContent(this, child);
+       }
+
+       @Override
+       public void remove() {
+               // TODO make it more robust
+               element.getParentNode().removeChild(element);
+
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               element.removeAttributeNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart());
+
+       }
+
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       public DomContentProvider getProvider() {
+               return provider;
+       }
+
+}
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 (file)
index 0000000..c5fde8d
--- /dev/null
@@ -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> xPath;
+
+       public DomContentProvider(Document document) {
+               this.document = document;
+               this.document.normalizeDocument();
+               XPathFactory xPathFactory = XPathFactory.newInstance();
+               xPath = new ThreadLocal<>() {
+
+                       @Override
+                       protected XPath initialValue() {
+                               // TODO set the document as namespace context?
+                               XPath res= xPathFactory.newXPath();
+                               res.setNamespaceContext(DomContentProvider.this);
+                               return res;
+                       }
+               };
+       }
+
+//     @Override
+//     public Content get() {
+//             return new DomContent(this, document.getDocumentElement());
+//     }
+
+//     public Element createElement(String name) {
+//             return document.createElementNS(null, name);
+//
+//     }
+
+       @Override
+       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+               if ("".equals(relativePath))
+                       return new DomContent(session, this, document.getDocumentElement());
+               if (relativePath.startsWith("/"))
+                       throw new IllegalArgumentException("Relative path cannot start with /");
+
+               String xPathExpression = '/' + relativePath;
+               if ("/".equals(mountPath))
+                       xPathExpression = "/cr:root" + xPathExpression;
+               try {
+                       NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
+                       if (nodes.getLength() > 1)
+                               throw new IllegalArgumentException(
+                                               "Multiple content found for " + relativePath + " under " + mountPath);
+                       if (nodes.getLength() == 0)
+                               throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found");
+                       Element element = (Element) nodes.item(0);
+                       return new DomContent(session, this, element);
+               } catch (XPathExpressionException e) {
+                       throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e);
+               }
+       }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+       @Override
+       public String getNamespaceURI(String prefix) {
+               return document.lookupNamespaceURI(prefix);
+       }
+
+       @Override
+       public String getPrefix(String namespaceURI) {
+               return document.lookupPrefix(namespaceURI);
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               List<String> res = new ArrayList<>();
+               res.add(getPrefix(namespaceURI));
+               return Collections.unmodifiableList(res).iterator();
+       }
+
+}
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 (file)
index 0000000..3b07081
--- /dev/null
@@ -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<Content> {
+       private final ProvidedSession session;
+       private final DomContentProvider provider;
+       private final NodeList nodeList;
+
+       private int currentIndex;
+       private final int length;
+       private Element nextElement = null;
+
+       public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+               this.session = session;
+               this.provider = provider;
+               this.nodeList = nodeList;
+
+               this.length = nodeList.getLength();
+               this.currentIndex = 0;
+               this.nextElement = findNext();
+       }
+
+       private Element findNext() {
+               while (currentIndex < length) {
+                       Node node = nodeList.item(currentIndex);
+                       if (node instanceof Element) {
+                               return (Element) node;
+                       }
+                       currentIndex++;
+               }
+               return null;
+       }
+
+       @Override
+       public boolean hasNext() {
+               return nextElement != null;
+       }
+
+       @Override
+       public Content next() {
+               if (nextElement == null)
+                       throw new NoSuchElementException();
+               DomContent result = new DomContent(session, provider, nextElement);
+               currentIndex++;
+               nextElement = findNext();
+               return result;
+       }
+
+}
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 (file)
index 69f2e94..0000000
+++ /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<String, ContentProvider> partitions = new TreeMap<>();
-
-       // TODO synchronize ?
-       private NavigableMap<String, String> prefixes = new TreeMap<>();
-
-       public CmsContentRepository() {
-               prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
-               prefixes.put("basic", CrName.CR_NAMESPACE_URI);
-               prefixes.put("owner", CrName.CR_NAMESPACE_URI);
-               prefixes.put("posix", CrName.CR_NAMESPACE_URI);
-       }
-
-       public void start() {
-
-       }
-
-       public void stop() {
-
-       }
-
-       /*
-        * REPOSITORY
-        */
-
-       @Override
-       public ContentSession get() {
-               return get(CmsContextImpl.getCmsContext().getDefaultLocale());
-       }
-
-       @Override
-       public ContentSession get(Locale locale) {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               return new CmsContentSession(subject, locale);
-       }
-
-       public void addProvider(String base, ContentProvider provider) {
-               partitions.put(base, provider);
-       }
-
-       public void registerPrefix(String prefix, String namespaceURI) {
-               String registeredUri = prefixes.get(prefix);
-               if (registeredUri == null) {
-                       prefixes.put(prefix, namespaceURI);
-                       return;
-               }
-               if (!registeredUri.equals(namespaceURI))
-                       throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
-               // do nothing if same namespace is already registered
-       }
-
-       /*
-        * NAMESPACE CONTEXT
-        */
-
-       /*
-        * SESSION
-        */
-
-       class CmsContentSession implements ProvidedSession {
-               private Subject subject;
-               private Locale locale;
-
-               public CmsContentSession(Subject subject, Locale locale) {
-                       this.subject = subject;
-                       this.locale = locale;
-               }
-
-               @Override
-               public Content get(String path) {
-                       Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
-                       String mountPath = entry.getKey();
-                       ContentProvider provider = entry.getValue();
-                       String relativePath = path.substring(mountPath.length());
-                       return provider.get(CmsContentSession.this, mountPath, relativePath);
-               }
-
-               @Override
-               public Subject getSubject() {
-                       return subject;
-               }
-
-               @Override
-               public Locale getLocale() {
-                       return locale;
-               }
-
-               @Override
-               public ProvidedRepository getRepository() {
-                       return CmsContentRepository.this;
-               }
-
-               /*
-                * NAMESPACE CONTEXT
-                */
-
-               @Override
-               public String findNamespace(String prefix) {
-                       return prefixes.get(prefix);
-               }
-
-               @Override
-               public Set<String> findPrefixes(String namespaceURI) {
-                       Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
-                                       .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
-
-                       return res;
-               }
-
-               @Override
-               public String findPrefix(String namespaceURI) {
-                       if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
-                               return CrName.CR_DEFAULT_PREFIX;
-                       return ProvidedSession.super.findPrefix(namespaceURI);
-               }
-
-       }
-
-}
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 (file)
index 0ba2409..0000000
+++ /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<QName, String> BASIC_KEYS;
-       private static final Map<QName, String> POSIX_KEYS;
-       static {
-               BASIC_KEYS = new HashMap<>();
-               BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
-               BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
-               BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
-               BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
-
-               POSIX_KEYS = new HashMap<>(BASIC_KEYS);
-               POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
-               POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
-               POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
-       }
-
-       private final ProvidedSession session;
-       private final FsContentProvider provider;
-       private final Path path;
-       private final boolean isRoot;
-       private final QName name;
-
-       protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
-               this.session = session;
-               this.provider = contentProvider;
-               this.path = path;
-               this.isRoot = contentProvider.isRoot(path);
-               // TODO check file names with ':' ?
-               if (isRoot)
-                       this.name = CrName.ROOT.get();
-               else
-                       this.name = session.parsePrefixedName(path.getFileName().toString());
-       }
-
-       protected FsContent(FsContent context, Path path) {
-               this(context.getSession(), context.getProvider(), path);
-       }
-
-       private boolean isPosix() {
-               return path.getFileSystem().supportedFileAttributeViews().contains("posix");
-       }
-
-       @Override
-       public QName getName() {
-               return name;
-       }
-
-       /*
-        * ATTRIBUTES
-        */
-
-       @Override
-       public <A> Optional<A> get(QName key, Class<A> clss) {
-               Object value;
-               try {
-                       // We need to add user: when accessing via Files#getAttribute
-                       value = Files.getAttribute(path, toFsAttributeKey(key));
-               } catch (IOException e) {
-                       throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
-               }
-               A res = null;
-               if (value instanceof FileTime) {
-                       if (clss.isAssignableFrom(FileTime.class))
-                               res = (A) value;
-                       Instant instant = ((FileTime) value).toInstant();
-                       if (Object.class.isAssignableFrom(clss)) {// plain object requested
-                               res = (A) instant;
-                       }
-                       // TODO perform trivial file conversion to other formats
-               }
-               if (value instanceof byte[]) {
-                       res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
-               }
-               if (res == null)
-                       try {
-                               res = (A) value;
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
-                       }
-               return Optional.of(res);
-       }
-
-       @Override
-       protected Iterable<QName> keys() {
-               Set<QName> result = new HashSet<>(isPosix() ? POSIX_KEYS.keySet() : BASIC_KEYS.keySet());
-               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
-               if (udfav != null) {
-                       try {
-                               for (String name : udfav.list()) {
-                                       result.add(session.parsePrefixedName(name));
-                               }
-                       } catch (IOException e) {
-                               throw new ContentResourceException("Cannot list attributes for " + path, e);
-                       }
-               }
-               return result;
-       }
-
-       @Override
-       protected void removeAttr(QName key) {
-               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
-               try {
-                       udfav.delete(session.toPrefixedName(key));
-               } catch (IOException e) {
-                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
-               }
-       }
-
-       @Override
-       public Object put(QName key, Object value) {
-               Object previous = get(key);
-               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
-               ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
-               try {
-                       int size = udfav.write(session.toPrefixedName(key), bb);
-               } catch (IOException e) {
-                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
-               }
-               return previous;
-       }
-
-       protected String toFsAttributeKey(QName key) {
-               if (POSIX_KEYS.containsKey(key))
-                       return POSIX_KEYS.get(key);
-               else
-                       return USER_ + session.toPrefixedName(key);
-       }
-
-       /*
-        * CONTENT OPERATIONS
-        */
-       @Override
-       public Iterator<Content> iterator() {
-               if (Files.isDirectory(path)) {
-                       try {
-                               return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
-                       } catch (IOException e) {
-                               throw new ContentResourceException("Cannot list " + path, e);
-                       }
-               } else {
-                       return Collections.emptyIterator();
-               }
-       }
-
-       @Override
-       public Content add(QName name, QName... classes) {
-               try {
-                       Path newPath = path.resolve(session.toPrefixedName(name));
-                       if (ContentName.contains(classes, CrName.COLLECTION.get()))
-                               Files.createDirectory(newPath);
-                       else
-                               Files.createFile(newPath);
-
-//             for(ContentClass clss:classes) {
-//                     Files.setAttribute(newPath, name, newPath, null)
-//             }
-                       return new FsContent(this, newPath);
-               } catch (IOException e) {
-                       throw new ContentResourceException("Cannot create new content", e);
-               }
-       }
-
-       @Override
-       public void remove() {
-               FsUtils.delete(path);
-       }
-
-       @Override
-       public Content getParent() {
-               if (isRoot)
-                       return null;// TODO deal with mounts
-               return new FsContent(this, path.getParent());
-       }
-
-       /*
-        * ACCESSORS
-        */
-       @Override
-       public ProvidedSession getSession() {
-               return session;
-       }
-
-       @Override
-       public FsContentProvider getProvider() {
-               return provider;
-       }
-
-}
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 (file)
index e5732da..0000000
+++ /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 (file)
index 0b68a77..0000000
+++ /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<QName> keys() {
-               // TODO implement an iterator?
-               Set<QName> result = new HashSet<>();
-               NamedNodeMap attributes = element.getAttributes();
-               for (int i = 0; i < attributes.getLength(); i++) {
-                       Attr attr = (Attr) attributes.item(i);
-                       QName key = toQName(attr);
-                       result.add(key);
-               }
-               return result;
-       }
-
-       @Override
-       public <A> Optional<A> get(QName key, Class<A> clss) {
-               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
-                               : key.getNamespaceURI();
-               if (element.hasAttributeNS(namespaceUriOrNull, key.getLocalPart())) {
-                       String value = element.getAttributeNS(namespaceUriOrNull, key.getLocalPart());
-                       if (clss.isAssignableFrom(String.class))
-                               return Optional.of((A) value);
-                       else
-                               return Optional.empty();
-               } else
-                       return null;
-       }
-
-       @Override
-       public Object put(QName key, Object value) {
-               Object previous = get(key);
-               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
-                               : key.getNamespaceURI();
-               element.setAttributeNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
-                               value.toString());
-               return previous;
-       }
-       
-       
-
-       @Override
-       public boolean hasText() {
-//             return element instanceof Text;
-               if (hasText != null)
-                       return hasText;
-               NodeList nodeList = element.getChildNodes();
-               if (nodeList.getLength() > 1) {
-                       hasText = false;
-                       return hasText;
-               }
-               nodes: for (int i = 0; i < nodeList.getLength(); i++) {
-                       Node node = nodeList.item(i);
-                       if (node instanceof Text) {
-                               Text text = (Text) node;
-                               if (!text.isElementContentWhitespace()) {
-                                       hasText = true;
-                                       break nodes;
-                               }
-                       }
-               }
-               if (hasText == null)
-                       hasText = false;
-               return hasText;
-//             if (text != null)
-//                     return true;
-//             text = element.getTextContent();
-//             return text != null;
-       }
-
-       @Override
-       public String getText() {
-               if (hasText())
-                       return element.getTextContent();
-               else
-                       return null;
-       }
-
-       /*
-        * CONTENT OPERATIONS
-        */
-
-       @Override
-       public Iterator<Content> iterator() {
-               NodeList nodeList = element.getChildNodes();
-               return new ElementIterator(session, provider, nodeList);
-       }
-
-       @Override
-       public Content getParent() {
-               Node parent = element.getParentNode();
-               if (parent == null)
-                       return null;
-               if (!(parent instanceof Element))
-                       throw new IllegalStateException("Parent is not an element");
-               return new DomContent(this, (Element) parent);
-       }
-
-       @Override
-       public Content add(QName name, QName... classes) {
-               // TODO consider classes
-               Document document = this.element.getOwnerDocument();
-               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
-                               : name.getNamespaceURI();
-               Element child = document.createElementNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
-               element.appendChild(child);
-               return new DomContent(this, child);
-       }
-
-       @Override
-       public void remove() {
-               // TODO make it more robust
-               element.getParentNode().removeChild(element);
-
-       }
-
-       @Override
-       protected void removeAttr(QName key) {
-               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
-                               : key.getNamespaceURI();
-               element.removeAttributeNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart());
-
-       }
-
-       public ProvidedSession getSession() {
-               return session;
-       }
-
-       public DomContentProvider getProvider() {
-               return provider;
-       }
-
-}
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 (file)
index dc200bf..0000000
+++ /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> xPath;
-
-       public DomContentProvider(Document document) {
-               this.document = document;
-               this.document.normalizeDocument();
-               XPathFactory xPathFactory = XPathFactory.newInstance();
-               xPath = new ThreadLocal<>() {
-
-                       @Override
-                       protected XPath initialValue() {
-                               // TODO set the document as namespace context?
-                               XPath res= xPathFactory.newXPath();
-                               res.setNamespaceContext(DomContentProvider.this);
-                               return res;
-                       }
-               };
-       }
-
-//     @Override
-//     public Content get() {
-//             return new DomContent(this, document.getDocumentElement());
-//     }
-
-//     public Element createElement(String name) {
-//             return document.createElementNS(null, name);
-//
-//     }
-
-       @Override
-       public Content get(ProvidedSession session, String mountPath, String relativePath) {
-               if ("".equals(relativePath))
-                       return new DomContent(session, this, document.getDocumentElement());
-               if (relativePath.startsWith("/"))
-                       throw new IllegalArgumentException("Relative path cannot start with /");
-
-               String xPathExpression = '/' + relativePath;
-               if ("/".equals(mountPath))
-                       xPathExpression = "/cr:root" + xPathExpression;
-               try {
-                       NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
-                       if (nodes.getLength() > 1)
-                               throw new IllegalArgumentException(
-                                               "Multiple content found for " + relativePath + " under " + mountPath);
-                       if (nodes.getLength() == 0)
-                               throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found");
-                       Element element = (Element) nodes.item(0);
-                       return new DomContent(session, this, element);
-               } catch (XPathExpressionException e) {
-                       throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e);
-               }
-       }
-
-       /*
-        * NAMESPACE CONTEXT
-        */
-       @Override
-       public String getNamespaceURI(String prefix) {
-               return document.lookupNamespaceURI(prefix);
-       }
-
-       @Override
-       public String getPrefix(String namespaceURI) {
-               return document.lookupPrefix(namespaceURI);
-       }
-
-       @Override
-       public Iterator<String> getPrefixes(String namespaceURI) {
-               List<String> res = new ArrayList<>();
-               res.add(getPrefix(namespaceURI));
-               return Collections.unmodifiableList(res).iterator();
-       }
-
-}
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 (file)
index db25b65..0000000
+++ /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<Content> {
-       private final ProvidedSession session;
-       private final DomContentProvider provider;
-       private final NodeList nodeList;
-
-       private int currentIndex;
-       private final int length;
-       private Element nextElement = null;
-
-       public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
-               this.session = session;
-               this.provider = provider;
-               this.nodeList = nodeList;
-
-               this.length = nodeList.getLength();
-               this.currentIndex = 0;
-               this.nextElement = findNext();
-       }
-
-       private Element findNext() {
-               while (currentIndex < length) {
-                       Node node = nodeList.item(currentIndex);
-                       if (node instanceof Element) {
-                               return (Element) node;
-                       }
-                       currentIndex++;
-               }
-               return null;
-       }
-
-       @Override
-       public boolean hasNext() {
-               return nextElement != null;
-       }
-
-       @Override
-       public Content next() {
-               if (nextElement == null)
-                       throw new NoSuchElementException();
-               DomContent result = new DomContent(session, provider, nextElement);
-               currentIndex++;
-               nextElement = findNext();
-               return result;
-       }
-
-}
diff --git a/pom.xml b/pom.xml
index 41f257e5097753d7db5fe2ce99b2ec5ac62bc86a..c0edf6c4df218ef3a7d5c69a3a17b279f8c86219 100644 (file)
--- a/pom.xml
+++ b/pom.xml
                <!-- Base -->
                <module>org.argeo.init</module>
                <module>org.argeo.util</module>
-               <!-- Eclipse -->
+               <!-- API -->
+               <module>org.argeo.api.acr</module>
+               <module>org.argeo.api.cms</module>
                <!-- CMS -->
-               <module>org.argeo.api</module>
                <module>org.argeo.cms.tp</module>
                <module>org.argeo.cms</module>
                <module>org.argeo.cms.pgsql</module>