Clarify ACR API
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Mar 2024 12:15:11 +0000 (13:15 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 10 Mar 2024 12:15:11 +0000 (13:15 +0100)
15 files changed:
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/QualifiedData.java
org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java
org.argeo.api.acr/src/org/argeo/api/acr/StructuredData.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentCmsEditable.java
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
org.argeo.cms/src/org/argeo/cms/acr/MountManager.java
org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/http/server/StaticHttpHandler.java

index d6af2fe3de301e044f3f95e468b853d31a89c6a0..c69e76a193077207091e58ad0fbc4a7d8f16eaa1 100644 (file)
@@ -4,7 +4,6 @@ import static org.argeo.api.acr.NamespaceUtils.unqualified;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -17,74 +16,17 @@ import javax.xml.namespace.QName;
  * whose nodes are named.
  */
 public interface Content extends QualifiedData<Content> {
-       /** The base of a repository path. */
-       String ROOT_PATH = "/";
+       /** The path separator: '/' */
+       char PATH_SEPARATOR = '/';
 
-       QName getName();
+       /** The base of a repository path. */
+       String ROOT_PATH = Character.toString(PATH_SEPARATOR);
 
        String getPath();
 
-       Content getParent();
-
-       /*
-        * ATTRIBUTES OPERATIONS
-        */
-
-       <A> Optional<A> get(QName key, Class<A> clss);
-
-       Class<?> getType(QName key);
-
-       boolean isMultiple(QName key);
-
-       <A> List<A> getMultiple(QName key, Class<A> clss);
-
-       /*
-        * ATTRIBUTES OPERATION HELPERS
-        */
-       default boolean containsKey(QNamed key) {
-               return containsKey(key.qName());
-       }
-
-       default <A> Optional<A> get(QNamed key, Class<A> clss) {
-               return get(key.qName(), clss);
-       }
-
-       default Object get(QNamed key) {
-               return get(key.qName());
-       }
-
-       default Object put(QNamed key, Object value) {
-               return put(key.qName(), value);
-       }
-
-       default Object remove(QNamed key) {
-               return remove(key.qName());
-       }
-
-       // TODO do we really need the helpers below?
-
-       default Object get(String key) {
-               return get(unqualified(key));
-       }
-
-       default Object put(String key, Object value) {
-               return put(unqualified(key), value);
-       }
-
-       default Object remove(String key) {
-               return remove(unqualified(key));
-       }
-
-       @SuppressWarnings("unchecked")
-       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");
-               }
-               List<A> res = getMultiple(key, type);
-               return res;
+       /** MUST be {@link Content#PATH_SEPARATOR}. */
+       default char getPathSeparator() {
+               return PATH_SEPARATOR;
        }
 
        /*
@@ -165,21 +107,9 @@ public interface Content extends QualifiedData<Content> {
                return res;
        }
 
-       /*
-        * SIBLINGS
-        */
-
-       default int getSiblingIndex() {
-               return 1;
-       }
-
        /*
         * DEFAULT METHODS
         */
-       default <A> A adapt(Class<A> clss) {
-               throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
-       }
-
        default <C extends Closeable> C open(Class<C> clss) throws IOException {
                throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName());
        }
@@ -191,19 +121,6 @@ public interface Content extends QualifiedData<Content> {
        /*
         * CHILDREN
         */
-
-       default boolean hasChild(QName name) {
-               for (Content child : this) {
-                       if (child.getName().equals(name))
-                               return true;
-               }
-               return false;
-       }
-
-       default boolean hasChild(QNamed name) {
-               return hasChild(name.qName());
-       }
-
        default Content anyOrAddChild(QName name, QName... classes) {
                Content child = anyChild(name);
                if (child != null)
@@ -215,92 +132,10 @@ public interface Content extends QualifiedData<Content> {
                return anyOrAddChild(unqualified(name), classes);
        }
 
-       /** Any child with this name, or null if there is none */
-       default Content anyChild(QName name) {
-               for (Content child : this) {
-                       if (child.getName().equals(name))
-                               return child;
-               }
-               return null;
-       }
-
-       default List<Content> children(QName name) {
-               List<Content> res = new ArrayList<>();
-               for (Content child : this) {
-                       if (child.getName().equals(name))
-                               res.add(child);
-               }
-               return res;
-       }
-
-       default List<Content> children(QNamed name) {
-               return children(name.qName());
-       }
-
-       default Optional<Content> soleChild(QNamed name) {
-               return soleChild(name.qName());
-       }
-
-       default Optional<Content> soleChild(QName name) {
-               List<Content> res = children(name);
-               if (res.isEmpty())
-                       return Optional.empty();
-               if (res.size() > 1)
-                       throw new IllegalStateException(this + " has multiple children with name " + name);
-               return Optional.of(res.get(0));
-       }
-
        default Content soleOrAddChild(QName name, QName... classes) {
                return soleChild(name).orElseGet(() -> this.add(name, classes));
        }
 
-       default Content child(QName name) {
-               return soleChild(name).orElseThrow();
-       }
-
-       default Content child(QNamed name) {
-               return child(name.qName());
-       }
-
-       /*
-        * ATTR AS STRING
-        */
-       /**
-        * Convenience method returning an attribute as a {@link String}.
-        * 
-        * @param key the attribute name
-        * @return the attribute value as a {@link String} or <code>null</code>.
-        * 
-        * @see Object#toString()
-        */
-       default String attr(QName key) {
-               return get(key, String.class).orElse(null);
-       }
-
-       /**
-        * Convenience method returning an attribute as a {@link String}.
-        * 
-        * @param key the attribute name
-        * @return the attribute value as a {@link String} or <code>null</code>.
-        * 
-        * @see Object#toString()
-        */
-       default String attr(QNamed key) {
-               return attr(key.qName());
-       }
-
-       /**
-        * Convenience method returning an attribute as a {@link String}.
-        * 
-        * @param key the attribute name
-        * @return the attribute value as a {@link String} or <code>null</code>.
-        * 
-        * @see Object#toString()
-        */
-       default String attr(String key) {
-               return attr(unqualified(key));
-       }
-
        /*
         * CONTEXT
         */
index 89ab665df1de9138c2fccf2ac93788bee3af3bc7..b2c7bbb90073ad8334fc2da29f673c10d9905ee0 100644 (file)
@@ -1,8 +1,187 @@
 package org.argeo.api.acr;
 
+import static org.argeo.api.acr.NamespaceUtils.unqualified;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
 import javax.xml.namespace.QName;
 
 /** A {@link StructuredData} whose attributes have qualified keys. */
 public interface QualifiedData<CHILD extends QualifiedData<CHILD>> extends StructuredData<QName, Object, CHILD> {
+       QName getName();
+
+       CHILD getParent();
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+
+       <A> Optional<A> get(QName key, Class<A> clss);
+
+       Class<?> getType(QName key);
+
+       boolean isMultiple(QName key);
+
+       <A> List<A> getMultiple(QName key, Class<A> clss);
+
+       /*
+        * PATH
+        */
+       char getPathSeparator();
+
+       /*
+        * ATTRIBUTES OPERATION HELPERS
+        */
+       default boolean containsKey(QNamed key) {
+               return containsKey(key.qName());
+       }
+
+       default <A> Optional<A> get(QNamed key, Class<A> clss) {
+               return get(key.qName(), clss);
+       }
+
+       default Object get(QNamed key) {
+               return get(key.qName());
+       }
+
+       default Object put(QNamed key, Object value) {
+               return put(key.qName(), value);
+       }
+
+       default Object remove(QNamed key) {
+               return remove(key.qName());
+       }
+
+       // TODO do we really need the helpers below?
+
+       default Object get(String key) {
+               return get(unqualified(key));
+       }
+
+       default Object put(String key, Object value) {
+               return put(unqualified(key), value);
+       }
+
+       default Object remove(String key) {
+               return remove(unqualified(key));
+       }
+
+       @SuppressWarnings("unchecked")
+       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");
+               }
+               List<A> res = getMultiple(key, type);
+               return res;
+       }
+
+       /*
+        * CHILDREN
+        */
+
+       default boolean hasChild(QName name) {
+               for (CHILD child : this) {
+                       if (child.getName().equals(name))
+                               return true;
+               }
+               return false;
+       }
+
+       default boolean hasChild(QNamed name) {
+               return hasChild(name.qName());
+       }
+
+       /** Any child with this name, or null if there is none */
+       default CHILD anyChild(QName name) {
+               for (CHILD child : this) {
+                       if (child.getName().equals(name))
+                               return child;
+               }
+               return null;
+       }
+
+       default List<CHILD> children(QName name) {
+               List<CHILD> res = new ArrayList<>();
+               for (CHILD child : this) {
+                       if (child.getName().equals(name))
+                               res.add(child);
+               }
+               return res;
+       }
+
+       default List<CHILD> children(QNamed name) {
+               return children(name.qName());
+       }
+
+       default Optional<CHILD> soleChild(QNamed name) {
+               return soleChild(name.qName());
+       }
+
+       default Optional<CHILD> soleChild(QName name) {
+               List<CHILD> res = children(name);
+               if (res.isEmpty())
+                       return Optional.empty();
+               if (res.size() > 1)
+                       throw new IllegalStateException(this + " has multiple children with name " + name);
+               return Optional.of(res.get(0));
+       }
+
+       default CHILD child(QName name) {
+               return soleChild(name).orElseThrow();
+       }
+
+       default CHILD child(QNamed name) {
+               return child(name.qName());
+       }
+
+       /*
+        * ATTR AS STRING
+        */
+       /**
+        * Convenience method returning an attribute as a {@link String}.
+        * 
+        * @param key the attribute name
+        * @return the attribute value as a {@link String} or <code>null</code>.
+        * 
+        * @see Object#toString()
+        */
+       default String attr(QName key) {
+               return get(key, String.class).orElse(null);
+       }
+
+       /**
+        * Convenience method returning an attribute as a {@link String}.
+        * 
+        * @param key the attribute name
+        * @return the attribute value as a {@link String} or <code>null</code>.
+        * 
+        * @see Object#toString()
+        */
+       default String attr(QNamed key) {
+               return attr(key.qName());
+       }
+
+       /**
+        * Convenience method returning an attribute as a {@link String}.
+        * 
+        * @param key the attribute name
+        * @return the attribute value as a {@link String} or <code>null</code>.
+        * 
+        * @see Object#toString()
+        */
+       default String attr(String key) {
+               return attr(unqualified(key));
+       }
 
+       /*
+        * SIBLINGS
+        */
+       default int getSiblingIndex() {
+               return 1;
+       }
 }
index dc47493384adfbbc095221b6a7a43482d5f87236..871275a487c2f310139600720a8c3131ae608af2 100644 (file)
@@ -23,6 +23,8 @@ public class RuntimeNamespaceContext implements NamespaceContext {
        public final static String XSD_DEFAULT_PREFIX = "xs";
        public final static String XSD_INSTANCE_DEFAULT_PREFIX = "xsi";
 
+       private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
+
        private NavigableMap<String, String> prefixes = new TreeMap<>();
        private NavigableMap<String, String> namespaces = new TreeMap<>();
 
@@ -58,7 +60,6 @@ public class RuntimeNamespaceContext implements NamespaceContext {
        /*
         * STATIC
         */
-       private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
 
        static {
                // Standard
@@ -66,6 +67,7 @@ public class RuntimeNamespaceContext implements NamespaceContext {
                register(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE);
 
                // Common
+               // FIXME shouldn't it be registered externally?
                register(XMLConstants.W3C_XML_SCHEMA_NS_URI, XSD_DEFAULT_PREFIX);
                register(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XSD_INSTANCE_DEFAULT_PREFIX);
 
index 4e38cf62d0f8131ef33805dff95ccb4e90ee362a..770823222cb6d93fd615d0f6841753e9fcb5c2e4 100644 (file)
@@ -4,5 +4,10 @@ import java.util.Map;
 
 /** A hierarchical structure of unnamed mappings. */
 public interface StructuredData<KEY, VALUE, CHILD> extends Map<KEY, VALUE>, Iterable<CHILD> {
-
+       /*
+        * DEFAULT METHODS
+        */
+       default <A> A adapt(Class<A> clss) {
+               throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
+       }
 }
index f1e5aaaa80f24af4dce505a935b342645d9910c8..5e718223601e80330e6617afd2ca04607488ca09 100644 (file)
@@ -41,10 +41,10 @@ public interface ProvidedContent extends Content {
        @Override
        default Optional<Content> getContent(String path) {
                String absolutePath;
-               if (path.startsWith(Content.ROOT_PATH)) {// absolute
+               if (path.startsWith(ROOT_PATH)) {// absolute
                        absolutePath = path;
                } else {// relative
-                       absolutePath = getPath() + '/' + path;
+                       absolutePath = getPath() + PATH_SEPARATOR + path;
                }
                return getSession().exists(absolutePath) ? Optional.of(getSession().get(absolutePath)) : Optional.empty();
        }
index 7278c285794f1cfac061f77a4840ba03e7a44dfd..870211f762bac951fa2113621558a892a4851ace 100644 (file)
@@ -5,7 +5,7 @@ import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.ux.AbstractCmsEditable;
 
 /** {@link CmsEditable} semantics for a {@link Content}. */
@@ -22,7 +22,7 @@ public class ContentCmsEditable extends AbstractCmsEditable {
                canEdit = providedContent.canEdit();
                session = providedContent.getSession();
                provider = providedContent.getProvider();
-               relativePath = ContentUtils.relativize(provider.getMountPath(), content.getPath());
+               relativePath = CmsContent.relativize(provider.getMountPath(), content.getPath());
        }
 
        @Override
index 1acdcc3809ba3914a4c256fb814baed41ce3d880..7eb4138ab64c8183a94143e8ff0780b0765540e4 100644 (file)
@@ -21,7 +21,7 @@ import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.util.LangUtils;
 
 /** Partial reference implementation of a {@link ProvidedContent}. */
-public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements CmsContent {
        private final ProvidedSession session;
 
        // cache
@@ -96,7 +96,7 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
                        if (CrName.root.qName().equals(name))
                                continue ancestors;
 
-                       path.append('/');
+                       path.append(PATH_SEPARATOR);
                        path.append(NamespaceUtils.toPrefixedName(name));
                        int siblingIndex = c.getSiblingIndex();
                        if (siblingIndex != 1)
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContent.java
new file mode 100644 (file)
index 0000000..184b2ce
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.cms.acr;
+
+import java.util.Objects;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+
+/** A content within a CMS system. */
+public interface CmsContent extends ProvidedContent {
+
+       /**
+        * Split a path (with {@link Content#PATH_SEPARATOR} separator) in an array of
+        * length 2, the first part being the parent path (which could be either
+        * absolute or relative), the second one being the last segment, (guaranteed to
+        * be without a '/').
+        */
+       static String[] getParentPath(String path) {
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               if (path.length() == 0)
+                       throw new IllegalArgumentException("Path cannot be empty");
+               ContentUtils.checkDoubleSlash(path);
+               int parentIndex = path.lastIndexOf(PATH_SEPARATOR);
+               if (parentIndex == path.length() - 1) {// trailing '/'
+                       path = path.substring(0, path.length() - 1);
+                       parentIndex = path.lastIndexOf(PATH_SEPARATOR);
+               }
+
+               if (parentIndex == -1) // no '/'
+                       return new String[] { "", path };
+
+               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : ContentUtils.PATH_SEPARATOR_STRING,
+                               path.substring(parentIndex + 1) };
+       }
+
+       /**
+        * Constructs a relative path between a base path and a given path.
+        * 
+        * @throws IllegalArgumentException if the base path is not an ancestor of the
+        *                                  path
+        */
+       static String relativize(String basePath, String path) throws IllegalArgumentException {
+               Objects.requireNonNull(basePath);
+               Objects.requireNonNull(path);
+               if (!path.startsWith(basePath))
+                       throw new IllegalArgumentException(basePath + " is not an ancestor of " + path);
+               String relativePath = path.substring(basePath.length());
+               if (relativePath.length() > 0 && relativePath.charAt(0) == PATH_SEPARATOR)
+                       relativePath = relativePath.substring(1);
+               return relativePath;
+       }
+
+}
index c782256e282acb94b97a7233ce0ddb4ad9d2cf0c..04a6fea6cdf15ad231127f66b79c7153981289d1 100644 (file)
@@ -74,7 +74,7 @@ class CmsContentSession implements ProvidedSession, UuidIdentified {
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = ContentUtils.relativize(mountPath, path);
+               String relativePath = CmsContent.relativize(mountPath, path);
                ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath);
                return content;
        }
@@ -85,7 +85,7 @@ class CmsContentSession implements ProvidedSession, UuidIdentified {
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = ContentUtils.relativize(mountPath, path);
+               String relativePath = CmsContent.relativize(mountPath, path);
                return contentProvider.exists(this, relativePath);
        }
 
@@ -113,7 +113,7 @@ class CmsContentSession implements ProvidedSession, UuidIdentified {
         */
        @Override
        public Content getMountPoint(String path) {
-               String[] parent = ContentUtils.getParentPath(path);
+               String[] parent = CmsContent.getParentPath(path);
                ProvidedContent mountParent = (ProvidedContent) get(parent[0]);
 //                     Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
                return mountParent.getMountPoint(parent[1]);
index b3f81454734be5e1d2e86cc1805c8d272fdad86c..5676e5c108b55e9a4a948b10ff1ca20f688d292d 100644 (file)
@@ -1,11 +1,12 @@
 package org.argeo.cms.acr;
 
+import static org.argeo.api.acr.Content.PATH_SEPARATOR;
+
 import java.io.PrintStream;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.StringTokenizer;
 import java.util.function.BiConsumer;
@@ -30,6 +31,10 @@ import org.argeo.cms.util.CurrentSubject;
 
 /** Utilities and routines around {@link Content}. */
 public class ContentUtils {
+       // Optimisations
+       static final String PATH_SEPARATOR_STRING = Character.toString(PATH_SEPARATOR);
+       private static final String DOUBLE_PATH_SEPARATOR = PATH_SEPARATOR_STRING + PATH_SEPARATOR_STRING;
+
        public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
                traverse(content, doIt, (Integer) null);
        }
@@ -75,61 +80,33 @@ public class ContentUtils {
 //             return t instanceof String;
 //     }
 
-       public static final char SLASH = '/';
-       public static final String SLASH_STRING = Character.toString(SLASH);
-       public static final String EMPTY = "";
-
-       /**
-        * Split a path (with '/' separator) in an array of length 2, the first part
-        * being the parent path (which could be either absolute or relative), the
-        * second one being the last segment, (guaranteed to be without a '/').
-        */
-       public static String[] getParentPath(String path) {
-               if (path == null)
-                       throw new IllegalArgumentException("Path cannot be null");
-               if (path.length() == 0)
-                       throw new IllegalArgumentException("Path cannot be empty");
-               checkDoubleSlash(path);
-               int parentIndex = path.lastIndexOf(SLASH);
-               if (parentIndex == path.length() - 1) {// trailing '/'
-                       path = path.substring(0, path.length() - 1);
-                       parentIndex = path.lastIndexOf(SLASH);
-               }
-
-               if (parentIndex == -1) // no '/'
-                       return new String[] { EMPTY, path };
-
-               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
-                               path.substring(parentIndex + 1) };
-       }
-
        public static String toPath(List<String> segments) {
                // TODO checks
-               StringJoiner sj = new StringJoiner("/");
+               StringJoiner sj = new StringJoiner(PATH_SEPARATOR_STRING);
                segments.forEach((s) -> sj.add(s));
                return sj.toString();
        }
 
-       public static List<String> toPathSegments(String path) {
+       static List<String> toPathSegments(String path) {
                List<String> res = new ArrayList<>();
-               if (EMPTY.equals(path) || Content.ROOT_PATH.equals(path))
+               if ("".equals(path) || Content.ROOT_PATH.equals(path))
                        return res;
                collectPathSegments(path, res);
                return res;
        }
 
-       private static void collectPathSegments(String path, List<String> segments) {
-               String[] parent = getParentPath(path);
-               if (EMPTY.equals(parent[1])) // root
+       static void collectPathSegments(String path, List<String> segments) {
+               String[] parent = CmsContent.getParentPath(path);
+               if ("".equals(parent[1])) // root
                        return;
                segments.add(0, parent[1]);
-               if (EMPTY.equals(parent[0])) // end
+               if ("".equals(parent[0])) // end
                        return;
                collectPathSegments(parent[0], segments);
        }
 
-       public static void checkDoubleSlash(String path) {
-               if (path.contains(SLASH + "" + SLASH))
+       static void checkDoubleSlash(String path) {
+               if (path.contains(DOUBLE_PATH_SEPARATOR))
                        throw new IllegalArgumentException("Path " + path + " contains //");
        }
 
@@ -156,7 +133,7 @@ public class ContentUtils {
 
        public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) {
                CmsDirectory directory = hierarchyUnit.getDirectory();
-               StringJoiner relativePath = new StringJoiner(SLASH_STRING);
+               StringJoiner relativePath = new StringJoiner(PATH_SEPARATOR_STRING);
                buildHierarchyUnitPath(hierarchyUnit, relativePath);
                String path = directoryPath(directory) + relativePath.toString();
                Content content = contentSession.get(path);
@@ -165,7 +142,7 @@ public class ContentUtils {
 
        /** The path to this {@link CmsDirectory}. Ends with a /. */
        private static String directoryPath(CmsDirectory directory) {
-               return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH;
+               return CmsContentRepository.DIRECTORY_BASE + PATH_SEPARATOR + directory.getName() + PATH_SEPARATOR;
        }
 
        /** Recursively build a relative path of a {@link HierarchyUnit}. */
@@ -189,7 +166,7 @@ public class ContentUtils {
                                return content;
                        }
                } else {
-                       String[] parentPath = getParentPath(path);
+                       String[] parentPath = CmsContent.getParentPath(path);
                        Content parent = createCollections(session, parentPath[0]);
                        Content content = parent.add(parentPath[1], DName.collection.qName());
                        return content;
@@ -219,23 +196,6 @@ public class ContentUtils {
                return CurrentSubject.callAs(cmsSession.getSubject(), () -> contentRepository.get());
        }
 
-       /**
-        * Constructs a relative path between a base path and a given path.
-        * 
-        * @throws IllegalArgumentException if the base path is not an ancestor of the
-        *                                  path
-        */
-       public static String relativize(String basePath, String path) throws IllegalArgumentException {
-               Objects.requireNonNull(basePath);
-               Objects.requireNonNull(path);
-               if (!path.startsWith(basePath))
-                       throw new IllegalArgumentException(basePath + " is not an ancestor of " + path);
-               String relativePath = path.substring(basePath.length());
-               if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
-                       relativePath = relativePath.substring(1);
-               return relativePath;
-       }
-
        /** A path in the node repository */
        public static String getDataPath(Content node) {
                // TODO make it more configurable?
index 90d621b761e57359ac14b02d2aeed2de47526146..39b2038fb02676bbf09677e0b46b34a966d6184d 100644 (file)
@@ -28,7 +28,7 @@ class MountManager {
                partitions.put(mountPath, contentProvider);
                if ("/".equals(mountPath))// root
                        return;
-               String[] parentPath = ContentUtils.getParentPath(mountPath);
+               String[] parentPath = CmsContent.getParentPath(mountPath);
                Content parent = systemSession.get(parentPath[0]);
                Content mount = parent.add(parentPath[1]);
                mount.put(CrName.mount.qName(), "true");
@@ -57,7 +57,7 @@ class MountManager {
                String mountPath = floorEntry.getKey();
                if (!path.startsWith(mountPath)) {
                        // FIXME make it more robust and find when there is no content provider
-                       String[] parent = ContentUtils.getParentPath(path);
+                       String[] parent = CmsContent.getParentPath(path);
                        return findContentProvider(parent[0]);
                        // throw new IllegalArgumentException("Path " + path + " doesn't have a content
                        // provider");
index 96e6eeaf3836c57887a6acb3c70802d2cf3a1909..b5915406e3411c045722a00a0f1c3b8b2152da2d 100644 (file)
@@ -15,7 +15,7 @@ import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.dav.DavResponse;
 import org.argeo.cms.http.HttpStatus;
 
@@ -41,7 +41,7 @@ public class DavContent extends AbstractContent {
 
        @Override
        public QName getName() {
-               String fileName = ContentUtils.getParentPath(uri.getPath())[1];
+               String fileName = CmsContent.getParentPath(uri.getPath())[1];
                ContentName name = NamespaceUtils.parsePrefixedName(provider, fileName);
                return name;
        }
@@ -49,7 +49,7 @@ public class DavContent extends AbstractContent {
        @Override
        public Content getParent() {
                try {
-                       String parentPath = ContentUtils.getParentPath(uri.getPath())[0];
+                       String parentPath = CmsContent.getParentPath(uri.getPath())[0];
                        URI parentUri = new URI(uri.getScheme(), uri.getHost(), parentPath, null);
                        return provider.getDavContent(getSession(), parentUri);
                } catch (URISyntaxException e) {
index 13b19aabb901a468b4c20ef25df9e3bcf82e3500..de21d32a60da859f41f44396e1c0738dd41ffff2 100644 (file)
@@ -42,7 +42,7 @@ import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.util.FsUtils;
 
 /** Content persisted as a filesystem {@link Path}. */
@@ -332,7 +332,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        String mountPath = provider.getMountPath();
                        if (mountPath == null || mountPath.equals("/"))
                                return null;
-                       String[] parent = ContentUtils.getParentPath(mountPath);
+                       String[] parent = CmsContent.getParentPath(mountPath);
                        return getSession().get(parent[0]);
                }
                return new FsContent(this, path.getParent());
index 0686be7cb59b2cf63d034fc8331a1d1884e39f08..1d751149101caaabad4e17b697b4bb699de94e9e 100644 (file)
@@ -34,7 +34,7 @@ import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.w3c.dom.Attr;
 import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
@@ -239,8 +239,8 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                        if (Content.ROOT_PATH.equals(mountPath)) {
                                return null;
                        }
-                       String[] parent = ContentUtils.getParentPath(mountPath);
-                       if (ContentUtils.EMPTY.equals(parent[0]))
+                       String[] parent = CmsContent.getParentPath(mountPath);
+                       if ("".equals(parent[0]))
                                return null;
                        return getSession().get(parent[0]);
                }
index 49cc242c7589fe8a5492b541fdb006db226482f0..8c5ccb048c06e1f933ce21fb74eedc74f56b03a1 100644 (file)
@@ -20,7 +20,7 @@ import java.util.Map;
 import java.util.NavigableMap;
 import java.util.TreeMap;
 
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.http.HttpHeader;
 import org.argeo.cms.http.HttpStatus;
 import org.argeo.cms.util.StreamUtils;
@@ -66,7 +66,7 @@ public class StaticHttpHandler implements HttpHandler {
                String mountPath = entry.getKey();
                if (!path.startsWith(mountPath)) {
                        // FIXME make it more robust and find when there is no content provider
-                       String[] parent = ContentUtils.getParentPath(path);
+                       String[] parent = CmsContent.getParentPath(path);
                        return findBind(parent[0]);
                }
                return entry;