import java.io.Closeable;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
* 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;
}
/*
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());
}
/*
* 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)
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
*/
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;
+ }
}
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<>();
/*
* STATIC
*/
- private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
static {
// Standard
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);
/** 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());
+ }
}
@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();
}
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}. */
canEdit = providedContent.canEdit();
session = providedContent.getSession();
provider = providedContent.getProvider();
- relativePath = ContentUtils.relativize(provider.getMountPath(), content.getPath());
+ relativePath = CmsContent.relativize(provider.getMountPath(), content.getPath());
}
@Override
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
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)
--- /dev/null
+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;
+ }
+
+}
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;
}
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);
}
*/
@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]);
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;
/** 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);
}
// 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 //");
}
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);
/** 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}. */
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;
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?
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");
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");
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;
@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;
}
@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) {
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}. */
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());
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;
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]);
}
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;
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;