From dcdbf77c4ec46f1dcad5d3fa011fe072e921f187 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 8 Jun 2022 11:29:10 +0200 Subject: [PATCH] Make ACR implementation more robust. --- .../argeo/cms/swt/acr/AcrContentTreeView.java | 2 +- .../src/org/argeo/cms/acr/ContentUtils.java | 16 +++++++--- .../cms/acr/SingleUserContentRepository.java | 23 ++++++++++++++ .../src/org/argeo/cms/acr/fs/FsContent.java | 14 +++++++-- .../argeo/cms/acr/fs/FsContentProvider.java | 31 +++++++++++++------ .../src/org/argeo/cms/acr/fs/PathSync.java | 3 -- .../src/org/argeo/cms/acr/xml/DomContent.java | 27 ++++++++++++++-- .../argeo/cms/acr/xml/DomContentProvider.java | 4 +++ .../src/org/argeo/cms/acr/xml/DomUtils.java | 10 ------ 9 files changed, 96 insertions(+), 34 deletions(-) diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java index ddf409e0b..4deef49c1 100644 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java @@ -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)); 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 d0ce5d153..2dcaeafaa 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java @@ -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 doIt) { - traverse(content, doIt, 0); + traverse(content, doIt, (Integer) null); } - public static void traverse(Content content, BiConsumer doIt, int currentDepth) { + public static void traverse(Content content, BiConsumer doIt, Integer maxDepth) { + doTraverse(content, doIt, 0, maxDepth); + } + + private static void doTraverse(Content content, BiConsumer 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 boolean isString(T t) { // return t instanceof String; // } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java index c44200243..0e3d1ad3f 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java @@ -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); + + } } 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 078cb50a8..e71acdabd 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 @@ -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 Optional get(QName key, Class 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 open(Class clss) throws IOException, IllegalArgumentException { if (InputStream.class.isAssignableFrom(clss)) { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java index 59f9f450d..62b20af3d 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java @@ -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 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); } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/PathSync.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/PathSync.java index d4f688187..427044e9a 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/PathSync.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/PathSync.java @@ -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> { private final URI sourceUri, targetUri; 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 b4931220b..ac863403d 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 @@ -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 adapt(Class 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 CompletableFuture write(Class clss) { if (String.class.isAssignableFrom(clss)) { CompletableFuture res = new CompletableFuture<>(); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java index 80523cb6c..423b60ead 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java @@ -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 */ diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java index 1029ba79f..3b5ff0dbc 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java @@ -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 { -- 2.30.2