Improve ACR
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 9 Sep 2022 07:10:09 +0000 (09:10 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 9 Sep 2022 07:10:09 +0000 (09:10 +0200)
12 files changed:
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrSessionNamespaceContext.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/CrName.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.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/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java

index b0326f48bca43a0b4bc7957b53a80a5e0254f74b..a4af35bc671437ad6b3d9d587817181a9ab7a480 100644 (file)
@@ -361,6 +361,11 @@ public class JcrContent extends AbstractContent {
                return QName.valueOf(name);
        }
 
+       @Override
+       public int getSiblingIndex() {
+               return Jcr.getIndex(getJcrNode());
+       }
+
        /*
         * STATIC UTLITIES
         */
index c85e3644190bb50d73d4e4b704cda3084a0d0af4..558086b5558f291dc47653d94730a82a1d57d920 100644 (file)
@@ -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<QName> 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<String> 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<QName> typesAsContentClasses(Node source, NamespaceContext jcrNamespaceContext)
+                       throws RepositoryException {
+               // TODO super types?
+               List<QName> 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<QName> 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 (file)
index 0000000..a55f4a3
--- /dev/null
@@ -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<String> getPrefixes(String namespaceURI) {
+               try {
+                       return Arrays.asList(session.getNamespacePrefix(namespaceURI)).iterator();
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               }
+       }
+}
index 4aac92de75b2317f78b83bea56329535c630ff81..3cdf8d7dbec044f6a3a0c7a40fbe092b407a2b86 100644 (file)
@@ -95,6 +95,10 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
         */
        List<QName> getContentClasses();
 
+       default void addContentClasses(QName... contentClass) {
+               throw new UnsupportedOperationException("Adding content classes to " + getPath() + " is not supported");
+       }
+
        /** AND */
        default boolean isContentClass(QName... contentClass) {
                List<QName> contentClasses = getContentClasses();
@@ -115,6 +119,14 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return false;
        }
 
+       /*
+        * SIBLINGS
+        */
+
+       default int getSiblingIndex() {
+               return 1;
+       }
+
        /*
         * DEFAULT METHODS
         */
index 025049966dfcd00bb6971ee9da2167dc0199990e..22aa82b9191db20f40698ee8c3a31b56c0bb9918 100644 (file)
@@ -15,6 +15,7 @@ public enum CrName {
         */
        uuid, // the UUID of a content
        mount,
+       cc, // content class
 
        /*
         * ATTRIBUTES FROM FILE SEMANTICS
index 52e8a205d7de27a3652cdb3031138948970f816c..29cf12e2b0c5f33a68a589aecdc3a397d9ba1399 100644 (file)
@@ -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
index 237219ad6a78ffa0a2d3fb1c2bf1612abd6d063f..43a4034159034cc8565e59aad94d54d42ab6b5d6 100644 (file)
@@ -13,6 +13,8 @@ public class ContentHierarchicalPart extends AbstractDataPart<Content, Content>
        @Override
        public List<Content> getChildren(Content content) {
                List<Content> res = new ArrayList<>();
+               if (isLeaf(content))
+                       return res;
                if (content == null)
                        return res;
                for (Iterator<Content> it = content.iterator(); it.hasNext();) {
@@ -22,6 +24,10 @@ public class ContentHierarchicalPart extends AbstractDataPart<Content, Content>
                return res;
        }
 
+       protected boolean isLeaf(Content content) {
+               return false;
+       }
+
        @Override
        public String getText(Content model) {
                try {
index 0aa4e9d4f783d770d9c2e1b44ecdef76ec854129..1cffef40ef5cc77f5be0783bfd5087ba48c1a565 100644 (file)
@@ -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<QName, Object> 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<QName, Object> impleme
        public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
                Object value = get(key);
                if (value == null)
-                       return null;
+                       return Optional.empty();
                if (value instanceof List) {
                        try {
                                List<A> res = (List<A>) value;
@@ -91,19 +92,25 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
 
        @Override
        public String getPath() {
-               if (_path != null)
-                       return _path;
+//             if (_path != null)
+//                     return _path;
                List<Content> 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<Content> ancestors, Content content) {
index 17144cfdb660cfceaf84c54016b46b6f44c1ac8e..e1fbcb1a9f440157b818a5c12dc8cf264be75c11 100644 (file)
@@ -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<String> toPathSegments(String path) {
                List<String> 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<String> 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);
        }
index 8cb90893637b91ebd63dbb2b8db441c389384d46..36b0cfe5ed051f05a129f40864b2ef4f05380001 100644 (file)
@@ -48,9 +48,11 @@ class MountManager {
        }
 
        synchronized ContentProvider findContentProvider(String path) {
+//             if (ContentUtils.EMPTY.equals(path))
+//                     return partitions.firstEntry().getValue();
                Map.Entry<String, ContentProvider> 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
index 55ef6ec46d89e7f4dbc01ef0b6ac2352c8901cf9..d61827d500eb03e9cebc1755d6266072ebc6ed18 100644 (file)
@@ -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<String> 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<QName> getContentClasses() {
                List<QName> res = new ArrayList<>();
+               Optional<List<String>> 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<String> 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
         */
index 30ecd8e82b649be7251473c204062937f5a3af8c..786b0d63261f2b298cde63194176f9a3e7c629af 100644 (file)
@@ -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<QName> getContentClasses() {
                List<QName> 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
         */