Make ACR implementation more robust.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 8 Jun 2022 09:29:10 +0000 (11:29 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 8 Jun 2022 09:29:10 +0000 (11:29 +0200)
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/fs/PathSync.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java

index ddf409e0b8d01609ddf75234e6e3af32cb01eccf..4deef49c1bd0e813651bc91def456d6248d93a25 100644 (file)
@@ -116,7 +116,7 @@ public class AcrContentTreeView extends Composite {
                shell.setText(basePath.toString());
                shell.setLayout(new FillLayout());
 
-               FsContentProvider contentSession = new FsContentProvider(null, basePath, true);
+               FsContentProvider contentSession = new FsContentProvider("/", basePath);
 //             GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
 
                shell.setSize(shell.computeSize(800, 600));
index d0ce5d1535bcf954242533f0a4d2febafdf16b55..2dcaeafaada5349810b0b659de825ffd3b390871 100644 (file)
@@ -7,16 +7,24 @@ import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 
+/** Utilities and routines around {@link Content}. */
 public class ContentUtils {
        public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
-               traverse(content, doIt, 0);
+               traverse(content, doIt, (Integer) null);
        }
 
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, Integer maxDepth) {
+               doTraverse(content, doIt, 0, maxDepth);
+       }
+
+       private static void doTraverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth,
+                       Integer maxDepth) {
                doIt.accept(content, currentDepth);
+               if (maxDepth != null && currentDepth == maxDepth)
+                       return;
                int nextDepth = currentDepth + 1;
                for (Content child : content) {
-                       traverse(child, doIt, nextDepth);
+                       doTraverse(child, doIt, nextDepth, maxDepth);
                }
        }
 
@@ -37,8 +45,6 @@ public class ContentUtils {
                }
        }
 
-       
-
 //     public static <T> boolean isString(T t) {
 //             return t instanceof String;
 //     }
index c44200243d83847d94d56be513330d719b4fa75b..0e3d1ad3fbf701b87b82f760548e2057de0342d4 100644 (file)
@@ -1,12 +1,17 @@
 package org.argeo.cms.acr;
 
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Locale;
 import java.util.Objects;
 
 import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
 
 import org.argeo.api.acr.ContentSession;
 import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.cms.acr.fs.FsContentProvider;
+import org.argeo.util.naming.LdapAttrs;
 
 /**
  * A standalone {@link ProvidedRepository} with a single {@link Subject} (which
@@ -69,4 +74,22 @@ public class SingleUserContentRepository extends AbstractContentRepository {
                return new CmsContentSession(this, subject, Locale.getDefault());
        }
 
+       public static void main(String... args) {
+               Path homePath = Paths.get(System.getProperty("user.home"));
+               String username = System.getProperty("user.name");
+               X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + ",dc=localhost");
+               Subject subject = new Subject();
+               subject.getPrincipals().add(principal);
+
+               SingleUserContentRepository contentRepository = new SingleUserContentRepository(subject);
+               FsContentProvider homeContentProvider = new FsContentProvider("/home", homePath);
+               contentRepository.addProvider(homeContentProvider);
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> contentRepository.stop(), "Shutdown content repository"));
+               contentRepository.start();
+
+               ContentSession contentSession = contentRepository.get();
+               ContentUtils.traverse(contentSession.get("/"), (c, depth) -> ContentUtils.print(c, System.out, depth, false),
+                               2);
+
+       }
 }
index 078cb50a8d38f28280385b7bf9e90f48c2c5aada..e71acdabd2bb973770fccfd5cd0bb5d54e5ce64b 100644 (file)
@@ -33,6 +33,7 @@ import org.argeo.cms.acr.AbstractContent;
 import org.argeo.cms.acr.ContentUtils;
 import org.argeo.util.FsUtils;
 
+/** Content persisted as a filesystem {@link Path}. */
 public class FsContent extends AbstractContent implements ProvidedContent {
        final static String USER_ = "user:";
 
@@ -61,7 +62,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                this.session = session;
                this.provider = contentProvider;
                this.path = path;
-               this.isRoot = contentProvider.isRoot(path);
+               this.isRoot = contentProvider.isMountRoot(path);
                // TODO check file names with ':' ?
                if (isRoot) {
                        String mountPath = provider.getMountPath();
@@ -72,7 +73,12 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                                this.name = CrName.ROOT.get();
                        }
                } else {
-                       QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+
+                       // TODO should we support prefixed name for known types?
+                       // 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);
                }
        }
@@ -94,6 +100,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
         * ATTRIBUTES
         */
 
+       @SuppressWarnings("unchecked")
        @Override
        public <A> Optional<A> get(QName key, Class<A> clss) {
                Object value;
@@ -178,7 +185,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
                try {
-                       int size = udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
+                       udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
@@ -266,6 +273,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                return new FsContent(this, path.getParent());
        }
 
+       @SuppressWarnings("unchecked")
        @Override
        public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
                if (InputStream.class.isAssignableFrom(clss)) {
index 59f9f450dec129dda9044f17472d23506b919c05..62b20af3df7570df6380a06671dedcc14e26a717 100644 (file)
@@ -9,6 +9,7 @@ import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.NavigableMap;
+import java.util.Objects;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
 
@@ -25,22 +26,33 @@ public class FsContentProvider implements ContentProvider {
 
        private final String mountPath;
        private final Path rootPath;
-       private final boolean isRoot;
+//     private final boolean isRoot;
 
        private NavigableMap<String, String> prefixes = new TreeMap<>();
 
-       public FsContentProvider(String mountPath, Path rootPath, boolean isRoot) {
+       public FsContentProvider(String mountPath, Path rootPath) {
+               Objects.requireNonNull(mountPath);
+               Objects.requireNonNull(rootPath);
+               
                this.mountPath = mountPath;
                this.rootPath = rootPath;
-               this.isRoot = isRoot;
+               // FIXME make it more robust
                initNamespaces();
        }
 
-       protected void initNamespaces() {
+//     @Deprecated
+//     public FsContentProvider(String mountPath, Path rootPath, boolean isRoot) {
+//             this.mountPath = mountPath;
+//             this.rootPath = rootPath;
+////           this.isRoot = isRoot;
+////           initNamespaces();
+//     }
+
+       private void initNamespaces() {
                try {
                        UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath,
                                        UserDefinedFileAttributeView.class);
-                       if(udfav==null)
+                       if (udfav == null)
                                return;
                        for (String name : udfav.list()) {
                                if (name.startsWith(XMLNS_)) {
@@ -85,16 +97,15 @@ public class FsContentProvider implements ContentProvider {
                }
 
        }
-       
-       
-@Override
+
+       @Override
        public String getMountPath() {
                return mountPath;
        }
 
-       boolean isRoot(Path path) {
+       boolean isMountRoot(Path path) {
                try {
-                       return isRoot && Files.isSameFile(rootPath, path);
+                       return Files.isSameFile(rootPath, path);
                } catch (IOException e) {
                        throw new ContentResourceException(e);
                }
index d4f6881877c005b02d1d12950f6f6e699ba6b723..427044e9ae290caaee85e53cd80f756a494e7ac8 100644 (file)
@@ -8,9 +8,6 @@ import java.nio.file.Paths;
 import java.nio.file.spi.FileSystemProvider;
 import java.util.concurrent.Callable;
 
-import org.argeo.cms.acr.fs.SyncFileVisitor;
-import org.argeo.cms.acr.fs.SyncResult;
-
 /** Synchronises two paths. */
 public class PathSync implements Callable<SyncResult<Path>> {
        private final URI sourceUri, targetUri;
index b4931220b2b8077979ab172f0ee5de53915fba6f..ac863403deed8138a298cb25a0efd5ce098c7d0a 100644 (file)
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.Text;
 
+/** Content persisted as a DOM element. */
 public class DomContent extends AbstractContent implements ProvidedContent {
 
        private final ProvidedSession session;
@@ -128,12 +129,31 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                Object previous = get(key);
                String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
                                : key.getNamespaceURI();
+               String prefixToUse = registerPrefixIfNeeded(key);
                element.setAttributeNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+                               namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(),
                                value.toString());
                return previous;
        }
 
+       protected String registerPrefixIfNeeded(QName name) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+                               : name.getNamespaceURI();
+               String prefixToUse;
+               if (namespaceUriOrNull != null) {
+                       String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
+                       if (registeredPrefix != null) {
+                               prefixToUse = registeredPrefix;
+                       } else {
+                               provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
+                               prefixToUse = name.getPrefix();
+                       }
+               } else {
+                       prefixToUse = null;
+               }
+               return prefixToUse;
+       }
+
        @Override
        public boolean hasText() {
 //             return element instanceof Text;
@@ -204,8 +224,9 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                Document document = this.element.getOwnerDocument();
                String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
                                : name.getNamespaceURI();
+               String prefixToUse = registerPrefixIfNeeded(name);
                Element child = document.createElementNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+                               namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart());
                element.appendChild(child);
                return new DomContent(this, child);
        }
@@ -226,6 +247,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
 
        }
 
+       @SuppressWarnings("unchecked")
        @Override
        public <A> A adapt(Class<A> clss) throws IllegalArgumentException {
                if (CharBuffer.class.isAssignableFrom(clss)) {
@@ -236,6 +258,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                return super.adapt(clss);
        }
 
+       @SuppressWarnings("unchecked")
        public <A> CompletableFuture<A> write(Class<A> clss) {
                if (String.class.isAssignableFrom(clss)) {
                        CompletableFuture<String> res = new CompletableFuture<>();
index 80523cb6c89898259252b06266dc1876918c641a..423b60eadd15f982cba3fb1cd4d115ea3f8c2646 100644 (file)
@@ -101,6 +101,10 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
                return mountPath;
        }
 
+       public void registerPrefix(String prefix, String namespace) {
+               DomUtils.addNamespace(document.getDocumentElement(), prefix, namespace);
+       }
+
        /*
         * NAMESPACE CONTEXT
         */
index 1029ba79f2de592ad9477208f8a082d884e9603e..3b5ff0dbceb241cdc0c1be774b5b66ac8ceebb3b 100644 (file)
@@ -1,17 +1,7 @@
 package org.argeo.cms.acr.xml;
 
-import java.io.IOException;
-import java.io.OutputStream;
-
 import javax.xml.XMLConstants;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
 
-import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
 public class DomUtils {