From: Mathieu Baudier Date: Fri, 9 Sep 2022 07:10:09 +0000 (+0200) Subject: Improve ACR X-Git-Tag: v2.3.10~49 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=975fb5e581d0650768afc68a0e839657f318e77a Improve ACR --- diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java index b0326f48b..a4af35bc6 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -361,6 +361,11 @@ public class JcrContent extends AbstractContent { return QName.valueOf(name); } + @Override + public int getSiblingIndex() { + return Jcr.getIndex(getJcrNode()); + } + /* * STATIC UTLITIES */ diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java index c85e36441..558086b55 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java @@ -4,16 +4,23 @@ import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import javax.jcr.Node; import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; +import javax.jcr.Value; import javax.jcr.nodetype.NodeType; import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -45,6 +52,9 @@ public class JcrContentUtils { public static void copyFiles(Node folder, Content collection, String... additionalCollectionTypes) { try { log.debug("Copy collection " + collection); + + NamespaceContext jcrNamespaceContext = new JcrSessionNamespaceContext(folder.getSession()); + nodes: for (NodeIterator it = folder.getNodes(); it.hasNext();) { Node node = it.nextNode(); String name = node.getName(); @@ -57,9 +67,14 @@ public class JcrContentUtils { Content subCol = collection.add(name, CrName.collection.qName()); copyFiles(node, subCol, additionalCollectionTypes); } else { + List contentClasses = typesAsContentClasses(node, jcrNamespaceContext); for (String collectionType : additionalCollectionTypes) { if (node.isNodeType(collectionType)) { - Content subCol = collection.add(name, CrName.collection.qName()); + contentClasses.add(CrName.collection.qName()); + Content subCol = collection.add(name, + contentClasses.toArray(new QName[contentClasses.size()])); + setAttributes(node, subCol, jcrNamespaceContext); +// setContentClasses(node, subCol, jcrNamespaceContext); copyFiles(node, subCol, additionalCollectionTypes); continue nodes; } @@ -71,12 +86,19 @@ public class JcrContentUtils { log.warn("Same name siblings not supported, skipping " + node); continue nodes; } - Content content = collection.add(qName, qName); + Content content = collection.add(qName, contentClasses.toArray(new QName[contentClasses.size()])); Source source = toSource(node); ((ProvidedContent) content).getSession().edit((s) -> { ((ProvidedSession) s).notifyModification((ProvidedContent) content); content.write(Source.class).complete(source); +// try { +// //setContentClasses(node, content, jcrNamespaceContext); +// } catch (RepositoryException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } }).toCompletableFuture().join(); + setAttributes(node, content, jcrNamespaceContext); // } else { // // ignore @@ -96,14 +118,15 @@ public class JcrContentUtils { // try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { // node.getSession().exportDocumentView(node.getPath(), out, true, false); -// DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder(); -// Document document; -// try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) { -// document = documentBuilder.parse(in); -// } -// cleanJcrDom(document); -// return new DOMSource(document); -// } catch (IOException | SAXException | ParserConfigurationException e) { +// System.out.println(new String(out.toByteArray(), StandardCharsets.UTF_8)); +//// DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder(); +//// Document document; +//// try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) { +//// document = documentBuilder.parse(in); +//// } +//// cleanJcrDom(document); +//// return new DOMSource(document); +// } catch (IOException e) { // throw new RuntimeException(e); // } @@ -133,7 +156,50 @@ public class JcrContentUtils { } + public static void setAttributes(Node source, Content target, NamespaceContext jcrNamespaceContext) + throws RepositoryException { + properties: for (PropertyIterator pit = source.getProperties(); pit.hasNext();) { + Property p = pit.nextProperty(); + // TODO migrate JCR title, last modified, etc. ? + if (p.getName().startsWith("jcr:")) + continue properties; + if (p.isMultiple()) { + List attr = new ArrayList<>(); + for (Value value : p.getValues()) { + attr.add(value.getString()); + } + target.put(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, p.getName()), attr); + } else { + target.put(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, p.getName()), p.getString()); + } + } + } + + public static List typesAsContentClasses(Node source, NamespaceContext jcrNamespaceContext) + throws RepositoryException { + // TODO super types? + List contentClasses = new ArrayList<>(); + contentClasses + .add(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, source.getPrimaryNodeType().getName())); + for (NodeType nodeType : source.getMixinNodeTypes()) { + contentClasses.add(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, nodeType.getName())); + } + // filter out JCR types + for (Iterator it = contentClasses.iterator(); it.hasNext();) { + QName type = it.next(); + if (type.getNamespaceURI().equals(JCR_NT_NAMESPACE_URI) + || type.getNamespaceURI().equals(JCR_MIX_NAMESPACE_URI)) { + it.remove(); + } + } + // target.addContentClasses(contentClasses.toArray(new + // QName[contentClasses.size()])); + return contentClasses; + } + static final String JCR_NAMESPACE_URI = "http://www.jcp.org/jcr/1.0"; + static final String JCR_NT_NAMESPACE_URI = "http://www.jcp.org/jcr/nt/1.0"; + static final String JCR_MIX_NAMESPACE_URI = "http://www.jcp.org/jcr/mix/1.0"; public static void cleanJcrDom(Document document) { Element documentElement = document.getDocumentElement(); @@ -176,6 +242,7 @@ public class JcrContentUtils { if (namespaceUri == null) continue attributes; if (JCR_NAMESPACE_URI.equals(namespaceUri)) { + // FIXME probably wrong to change attributes length element.removeAttributeNode(attr); continue attributes; } diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrSessionNamespaceContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrSessionNamespaceContext.java new file mode 100644 index 000000000..a55f4a359 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrSessionNamespaceContext.java @@ -0,0 +1,46 @@ +package org.argeo.cms.jcr.acr; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.xml.namespace.NamespaceContext; + +import org.argeo.jcr.JcrException; + +/** A {@link NamespaceContext} based on a JCR {@link Session}. */ +public class JcrSessionNamespaceContext implements NamespaceContext { + private final Session session; + + public JcrSessionNamespaceContext(Session session) { + this.session = session; + } + + @Override + public String getNamespaceURI(String prefix) { + try { + return session.getNamespaceURI(prefix); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + @Override + public String getPrefix(String namespaceURI) { + try { + return session.getNamespacePrefix(namespaceURI); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + try { + return Arrays.asList(session.getNamespacePrefix(namespaceURI)).iterator(); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } +} 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 index 4aac92de7..3cdf8d7db 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java @@ -95,6 +95,10 @@ public interface Content extends Iterable, Map { */ List getContentClasses(); + default void addContentClasses(QName... contentClass) { + throw new UnsupportedOperationException("Adding content classes to " + getPath() + " is not supported"); + } + /** AND */ default boolean isContentClass(QName... contentClass) { List contentClasses = getContentClasses(); @@ -115,6 +119,14 @@ public interface Content extends Iterable, Map { return false; } + /* + * SIBLINGS + */ + + default int getSiblingIndex() { + return 1; + } + /* * DEFAULT METHODS */ 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 index 025049966..22aa82b91 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java @@ -15,6 +15,7 @@ public enum CrName { */ uuid, // the UUID of a content mount, + cc, // content class /* * ATTRIBUTES FROM FILE SEMANTICS 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 index 52e8a205d..29cf12e2b 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java @@ -35,6 +35,13 @@ public interface CmsConstants { String GUESTS_WORKSPACE = "guests"; String PUBLIC_WORKSPACE = "public"; String SECURITY_WORKSPACE = "security"; + String MIGRATION_WORKSPACE = "migration"; + + /* + * ACR CONVENTIONS + */ + String SRV_BASE = "/srv"; + /* * BASE DNs diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java index 237219ad6..43a403415 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java @@ -13,6 +13,8 @@ public class ContentHierarchicalPart extends AbstractDataPart @Override public List getChildren(Content content) { List res = new ArrayList<>(); + if (isLeaf(content)) + return res; if (content == null) return res; for (Iterator it = content.iterator(); it.hasNext();) { @@ -22,6 +24,10 @@ public class ContentHierarchicalPart extends AbstractDataPart return res; } + protected boolean isLeaf(Content content) { + return false; + } + @Override public String getText(Content model) { try { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java index 0aa4e9d4f..1cffef40e 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java @@ -14,6 +14,7 @@ import javax.xml.namespace.QName; import org.argeo.api.acr.Content; import org.argeo.api.acr.CrName; +import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.util.LangUtils; @@ -23,7 +24,7 @@ public abstract class AbstractContent extends AbstractMap impleme private final ProvidedSession session; // cache - private String _path = null; +// private String _path = null; public AbstractContent(ProvidedSession session) { this.session = session; @@ -57,7 +58,7 @@ public abstract class AbstractContent extends AbstractMap impleme public Optional> getMultiple(QName key, Class clss) { Object value = get(key); if (value == null) - return null; + return Optional.empty(); if (value instanceof List) { try { List res = (List) value; @@ -91,19 +92,25 @@ public abstract class AbstractContent extends AbstractMap impleme @Override public String getPath() { - if (_path != null) - return _path; +// if (_path != null) +// return _path; List ancestors = new ArrayList<>(); collectAncestors(ancestors, this); StringBuilder path = new StringBuilder(); - for (Content c : ancestors) { + ancestors: for (Content c : ancestors) { QName name = c.getName(); - // FIXME - if (!CrName.root.qName().equals(name)) - path.append('/').append(name); + if (CrName.root.qName().equals(name)) + continue ancestors; + + path.append('/'); + path.append(NamespaceUtils.toPrefixedName(name)); + int siblingIndex = c.getSiblingIndex(); + if (siblingIndex != 1) + path.append('[').append(siblingIndex).append(']'); } - _path = path.toString(); - return _path; +// _path = path.toString(); +// return _path; + return path.toString(); } private void collectAncestors(List ancestors, Content content) { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java index 17144cfdb..e1fbcb1a9 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java @@ -64,6 +64,7 @@ public class ContentUtils { public static final char SLASH = '/'; public static final String ROOT_SLASH = "" + SLASH; + public static final String EMPTY = ""; /** * Split a path (with '/' separator) in an array of length 2, the first part @@ -83,7 +84,7 @@ public class ContentUtils { } if (parentIndex == -1) // no '/' - return new String[] { "", path }; + return new String[] { EMPTY, path }; return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH, path.substring(parentIndex + 1) }; @@ -98,7 +99,7 @@ public class ContentUtils { public static List toPathSegments(String path) { List res = new ArrayList<>(); - if ("".equals(path) || ROOT_SLASH.equals(path)) + if (EMPTY.equals(path) || ROOT_SLASH.equals(path)) return res; collectPathSegments(path, res); return res; @@ -106,10 +107,10 @@ public class ContentUtils { private static void collectPathSegments(String path, List segments) { String[] parent = getParentPath(path); - if ("".equals(parent[1])) // root + if (EMPTY.equals(parent[1])) // root return; segments.add(0, parent[1]); - if ("".equals(parent[0])) // end + if (EMPTY.equals(parent[0])) // end return; collectPathSegments(parent[0], segments); } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java index 8cb908936..36b0cfe5e 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java @@ -48,9 +48,11 @@ class MountManager { } synchronized ContentProvider findContentProvider(String path) { +// if (ContentUtils.EMPTY.equals(path)) +// return partitions.firstEntry().getValue(); Map.Entry entry = partitions.floorEntry(path); if (entry == null) - throw new IllegalArgumentException("No entry provider found for " + path); + throw new IllegalArgumentException("No entry provider found for path '" + path + "'"); String mountPath = entry.getKey(); if (!path.startsWith(mountPath)) { // FIXME make it more robust and find when there is no content provider 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 index 55ef6ec46..d61827d50 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import javax.xml.namespace.QName; @@ -73,7 +74,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { // TODO check file names with ':' ? if (isMountBase) { String mountPath = provider.getMountPath(); - if (mountPath != null && !mountPath.equals("/")) { + if (mountPath != null && !mountPath.equals(ContentUtils.ROOT_SLASH)) { Content mountPoint = session.getMountPoint(mountPath); this.name = mountPoint.getName(); } else { @@ -82,9 +83,8 @@ public class FsContent extends AbstractContent implements ProvidedContent { } else { // TODO should we support prefixed name for known types? - // QName providerName = NamespaceUtils.parsePrefixedName(provider, - // path.getFileName().toString()); - QName providerName = new QName(path.getFileName().toString()); + QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString()); +// QName providerName = new QName(path.getFileName().toString()); // TODO remove extension if mounted? this.name = new ContentName(providerName, session); } @@ -147,7 +147,17 @@ public class FsContent extends AbstractContent implements ProvidedContent { // TODO perform trivial file conversion to other formats } if (value instanceof byte[]) { - res = (A) new String((byte[]) value, StandardCharsets.UTF_8); + String str = new String((byte[]) value, StandardCharsets.UTF_8); + String[] arr = str.split("\n"); + if (arr.length == 1) { + res = (A) arr[0]; + } else { + List lst = new ArrayList<>(); + for (String s : arr) { + lst.add(s); + } + res = (A) lst; + } } if (res == null) try { @@ -189,8 +199,20 @@ public class FsContent extends AbstractContent implements ProvidedContent { @Override public Object put(QName key, Object value) { Object previous = get(key); + + String toWrite; + if (value instanceof List) { + StringJoiner sj = new StringJoiner("\n"); + for (Object obj : (List) value) { + sj.add(obj.toString()); + } + toWrite = sj.toString(); + } else { + toWrite = value.toString(); + } + UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); - ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8)); + ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8)); try { udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb); } catch (IOException e) { @@ -252,6 +274,8 @@ public class FsContent extends AbstractContent implements ProvidedContent { throw new ContentResourceException("Cannot create new content", e); } + if (classes.length > 0) + fsContent.addContentClasses(classes); if (getSession().getRepository().shouldMount(classes)) { ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true, classes); @@ -313,12 +337,32 @@ public class FsContent extends AbstractContent implements ProvidedContent { @Override public List getContentClasses() { List res = new ArrayList<>(); + Optional> value = getMultiple(CrName.cc.qName(), String.class); + if (!value.isEmpty()) { + for (String s : value.get()) { + QName name = NamespaceUtils.parsePrefixedName(provider, s); + res.add(name); + } + } if (Files.isDirectory(path)) res.add(CrName.collection.qName()); - // TODO add other types return res; } + @Override + public void addContentClasses(QName... contentClass) { + List toWrite = new ArrayList<>(); + for (QName cc : getContentClasses()) { + if (cc.equals(CrName.collection.qName())) + continue; // skip + toWrite.add(NamespaceUtils.toPrefixedName(provider, cc)); + } + for (QName cc : contentClass) { + toWrite.add(NamespaceUtils.toPrefixedName(provider, cc)); + } + put(CrName.cc.qName(), toWrite); + } + /* * ACCESSORS */ 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 index 30ecd8e82..786b0d632 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -20,6 +21,7 @@ import javax.xml.transform.dom.DOMSource; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; +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; @@ -55,16 +57,24 @@ public class DomContent extends AbstractContent implements ProvidedContent { @Override public QName getName() { - if (element.getParentNode() == null) {// root + if (isLocalRoot()) {// root String mountPath = provider.getMountPath(); if (mountPath != null) { + if (ContentUtils.ROOT_SLASH.equals(mountPath)) { + return CrName.root.qName(); + } Content mountPoint = getSession().getMountPoint(mountPath); - return mountPoint.getName(); + QName mountPointName = mountPoint.getName(); + return mountPointName; } } return toQName(this.element); } + protected boolean isLocalRoot() { + return element.getParentNode() == null || element.getParentNode() instanceof Document; + } + protected QName toQName(Node node) { String prefix = node.getPrefix(); if (prefix == null) { @@ -212,15 +222,18 @@ public class DomContent extends AbstractContent implements ProvidedContent { @Override public Content getParent() { Node parentNode = element.getParentNode(); - if (parentNode == null) { + if (isLocalRoot()) { String mountPath = provider.getMountPath(); if (mountPath == null) return null; + if (ContentUtils.ROOT_SLASH.equals(mountPath)) { + return null; + } String[] parent = ContentUtils.getParentPath(mountPath); + if (ContentUtils.EMPTY.equals(parent[0])) + return null; return getSession().get(parent[0]); } - if (parentNode instanceof Document) - return null; if (!(parentNode instanceof Element)) throw new IllegalStateException("Parent is not an element"); return new DomContent(this, (Element) parentNode); @@ -308,7 +321,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { } // parentNode.replaceChild(resultNode, element); // element = (Element)resultNode; - + } catch (DOMException | TransformerException e) { throw new RuntimeException("Cannot write to element", e); } @@ -318,16 +331,53 @@ public class DomContent extends AbstractContent implements ProvidedContent { return super.write(clss); } + @Override + public int getSiblingIndex() { + Node curr = element.getPreviousSibling(); + int count = 1; + while (curr != null) { + if (curr instanceof Element) { + if (Objects.equals(curr.getNamespaceURI(), element.getNamespaceURI()) + && Objects.equals(curr.getLocalName(), element.getLocalName())) { + count++; + } + } + curr = curr.getPreviousSibling(); + } + return count; + } + /* * TYPING */ @Override public List getContentClasses() { List res = new ArrayList<>(); - res.add(getName()); + if (isLocalRoot()) { + String mountPath = provider.getMountPath(); + if (mountPath != null) { + Content mountPoint = getSession().getMountPoint(mountPath); + res.addAll(mountPoint.getContentClasses()); + } + } else { + res.add(getName()); + } return res; } + @Override + public void addContentClasses(QName... contentClass) { + if (isLocalRoot()) { + String mountPath = provider.getMountPath(); + if (mountPath != null) { + Content mountPoint = getSession().getMountPoint(mountPath); + mountPoint.addContentClasses(contentClass); + } + } else { + super.addContentClasses(contentClass); + } + } + /* * MOUNT MANAGEMENT */