Improve ACR, introduce migration from JCR.
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 25 Aug 2022 10:07:43 +0000 (12:07 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 25 Aug 2022 10:07:43 +0000 (12:07 +0200)
17 files changed:
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java
org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java
org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.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/TypesManager.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/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java
org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java

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
new file mode 100644 (file)
index 0000000..702f4d6
--- /dev/null
@@ -0,0 +1,172 @@
+package org.argeo.cms.jcr.acr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.HashSet;
+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.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+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.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/** Utilities around integration between JCR and ACR. */
+public class JcrContentUtils {
+       private final static CmsLog log = CmsLog.getLog(JcrContentUtils.class);
+
+       public static void copyFiles(Node folder, Content collection) {
+               try {
+                       nodes: for (NodeIterator it = folder.getNodes(); it.hasNext();) {
+                               Node node = it.nextNode();
+                               String name = node.getName();
+                               if (node.isNodeType(NodeType.NT_FILE)) {
+                                       Content file = collection.anyOrAddChild(new ContentName(name));
+                                       try (InputStream in = JcrUtils.getFileAsStream(node)) {
+                                               file.write(InputStream.class).complete(in);
+                                       }
+                               } else if (node.isNodeType(NodeType.NT_FOLDER)) {
+                                       Content subCol = collection.add(name, CrName.collection.qName());
+                                       copyFiles(node, subCol);
+                               } else {
+                                       QName qName = NamespaceUtils.parsePrefixedName(name);
+                                       if (NamespaceUtils.hasNamespace(qName)) {
+                                               if (node.getIndex() > 1) {
+                                                       log.warn("Same name siblings not supported, skipping " + node);
+                                                       continue nodes;
+                                               }
+                                               Content content = collection.add(qName, qName);
+                                               Source source = toSource(node);
+                                               ((ProvidedContent) content).getSession().edit((s) -> {
+                                                       ((ProvidedSession) s).notifyModification((ProvidedContent) content);
+                                                       content.write(Source.class).complete(source);
+//                                                     ContentUtils.traverse(content,
+//                                                                     (c, depth) -> ContentUtils.print(c, System.out, depth, false));
+                                               }).toCompletableFuture().join();
+
+                                       } else {
+                                               // ignore
+                                               log.debug(() -> "Ignored " + node);
+                                               continue nodes;
+                                       }
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot copy files from " + folder + " to " + collection, e);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot copy files from " + folder + " to " + collection, e);
+               }
+       }
+
+       private static Source toSource(Node node) {
+               try (PipedInputStream in = new PipedInputStream();) {
+
+                       CompletableFuture<Document> toDo = CompletableFuture.supplyAsync(() -> {
+                               try {
+                                       DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
+                                       return documentBuilder.parse(in);
+                               } catch (ParserConfigurationException | SAXException | IOException e) {
+                                       throw new RuntimeException("Cannot parse", e);
+                               }
+                       });
+
+                       // TODO optimise
+                       try (PipedOutputStream out = new PipedOutputStream(in)) {
+                               node.getSession().exportDocumentView(node.getPath(), out, true, false);
+                       } catch (IOException | RepositoryException e) {
+                               throw new RuntimeException("Cannot export " + node + " in workspace " + Jcr.getWorkspaceName(node), e);
+                       }
+                       Document document = toDo.get();
+                       cleanJcrDom(document);
+                       return new DOMSource(document);
+               } catch (IOException | InterruptedException | ExecutionException e1) {
+                       throw new RuntimeException("Cannot parse", e1);
+               }
+
+       }
+
+       static final String JCR_NAMESPACE_URI = "http://www.jcp.org/jcr/1.0";
+
+       public static void cleanJcrDom(Document document) {
+               Element documentElement = document.getDocumentElement();
+               Set<String> namespaceUris = new HashSet<>();
+               cleanJcrDom(documentElement, namespaceUris);
+
+               // remove unused namespaces
+               NamedNodeMap attrs = documentElement.getAttributes();
+               Set<Attr> toRemove = new HashSet<>();
+               for (int i = 0; i < attrs.getLength(); i++) {
+                       Attr attr = (Attr) attrs.item(i);
+//                     log.debug("Check "+i+" " + attr);
+                       String prefix = attr.getPrefix();
+                       if (prefix != null && prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
+                               String namespaceUri = attr.getValue();
+                               if (!namespaceUris.contains(namespaceUri)) {
+                                       toRemove.add(attr);
+                                       //log.debug("Removing "+i+" " + namespaceUri);
+                               }
+                       }
+               }
+               for(Attr attr:toRemove)
+                       documentElement.removeAttributeNode(attr);
+
+       }
+
+       private static void cleanJcrDom(Element element, Set<String> namespaceUris) {
+               NodeList children = element.getElementsByTagName("*");
+               for (int i = 0; i < children.getLength(); i++) {
+                       Element child = (Element) children.item(i);
+                       if (!namespaceUris.contains(child.getNamespaceURI()))
+                               namespaceUris.add(child.getNamespaceURI());
+                       cleanJcrDom(child, namespaceUris);
+               }
+
+               NamedNodeMap attrs = element.getAttributes();
+               attributes: for (int i = 0; i < attrs.getLength(); i++) {
+                       Attr attr = (Attr) attrs.item(i);
+                       String namespaceUri = attr.getNamespaceURI();
+                       if (namespaceUri == null)
+                               continue attributes;
+                       if (JCR_NAMESPACE_URI.equals(namespaceUri)) {
+                               element.removeAttributeNode(attr);
+                               continue attributes;
+                       }
+                       if (!namespaceUris.contains(namespaceUri))
+                               namespaceUris.add(attr.getNamespaceURI());
+
+               }
+
+       }
+
+       /** singleton */
+       private JcrContentUtils() {
+       }
+
+}
index 8e2ac896080ece0c9b4e37cdde3fa8b6e09a0112..977adac584989f576c0482f5292a849e2c225650 100644 (file)
@@ -10,6 +10,7 @@ import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
+import org.argeo.api.acr.spi.ProvidedRepository;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.jcr.CmsJcrUtils;
 import org.argeo.jcr.Jcr;
@@ -29,6 +30,8 @@ public abstract class AbstractMaintenanceService {
        private UserAdmin userAdmin;
        private WorkTransaction userTransaction;
 
+       private ProvidedRepository contentRepository;
+
        public void init() {
                makeSureRolesExists(getRequiredRoles());
                configureStandardRoles();
@@ -217,4 +220,13 @@ public abstract class AbstractMaintenanceService {
                this.userTransaction = userTransaction;
        }
 
+       public void setContentRepository(ProvidedRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+       protected ProvidedRepository getContentRepository() {
+               return contentRepository;
+       }
+
+
 }
index 0fdc14fe30e9f0146afbc8f8dde42187086d0200..4aac92de75b2317f78b83bea56329535c630ff81 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.api.acr;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -129,6 +130,34 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                throw new UnsupportedOperationException("Cannot write 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 Content anyOrAddChild(QName name, QName... classes) {
+               Content child = anyChild(name);
+               if (child != null)
+                       return child;
+               return this.add(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;
+       }
+
        /*
         * CONVENIENCE METHODS
         */
index 904d50ed5e43dd0ad5da31020330d5a7cfb92fc0..64566ea0c9dbffe785e7cf34248105a82d0ae659 100644 (file)
@@ -61,8 +61,8 @@ public class NamespaceUtils {
 
        };
 
-       /** singleton */
-       private NamespaceUtils() {
+       public static boolean hasNamespace(QName qName) {
+               return !qName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI);
        }
 
        /*
@@ -127,4 +127,8 @@ public class NamespaceUtils {
                return prefixes.iterator();
        }
 
+       /** singleton */
+       private NamespaceUtils() {
+       }
+
 }
index 1d61c6c674c34506643ad5e5cabdedeacb3ea2de..dda1dac1f7fa118ae4247b93e21bf7963c11e689 100644 (file)
@@ -39,4 +39,8 @@ public interface CmsSession {
        void registerView(String uid, Object view);
 
        void addOnCloseCallback(Consumer<CmsSession> onClose);
+
+       public static boolean hasCmsSession(Subject subject) {
+               return !subject.getPrivateCredentials(CmsSessionId.class).isEmpty();
+       }
 }
index bc12bcbe20694c2d735453e9b57b96b5239d5e33..70c50ee9b8eef5ba8f108bcefbdb942ee715855d 100644 (file)
@@ -2,6 +2,8 @@ package org.argeo.api.cms;
 
 import java.security.Principal;
 
+import javax.security.auth.Subject;
+
 /** Allows to modify any data. */
 public final class DataAdminPrincipal implements Principal {
        private final String name = CmsConstants.ROLE_DATA_ADMIN;
@@ -26,4 +28,7 @@ public final class DataAdminPrincipal implements Principal {
                return name.toString();
        }
 
+       public static boolean isDataAdmin(Subject subject) {
+               return !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
+       }
 }
index 26ba6be2a18b503ee637224354bbd67cca981d72..a2d8069dd26e7c1698ca8884efe828c2c73f10a4 100644 (file)
@@ -216,4 +216,10 @@ public abstract class AbstractContentRepository implements ProvidedRepository {
        TypesManager getTypesManager() {
                return typesManager;
        }
+
+       CmsContentSession getSystemSession() {
+               return systemSession;
+       }
+       
+       
 }
index 6285710e8a0eff395acf8ab73a3877f66ae05c73..474e07232d68d2bdae08eb3e456672b4010a2aad 100644 (file)
@@ -13,9 +13,11 @@ import org.argeo.api.acr.spi.ProvidedRepository;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.DataAdminPrincipal;
 import org.argeo.api.uuid.UuidFactory;
 import org.argeo.cms.auth.CurrentUser;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.util.CurrentSubject;
 
 /**
  * Multi-session {@link ProvidedRepository}, integrated with a CMS.
@@ -40,7 +42,14 @@ public class CmsContentRepository extends AbstractContentRepository {
 
        @Override
        public ContentSession get(Locale locale) {
-               // Subject subject = Subject.getSubject(AccessController.getContext());
+               if (!CmsSession.hasCmsSession(CurrentSubject.current())) {
+                       if (DataAdminPrincipal.isDataAdmin(CurrentSubject.current())) {
+                               // TODO open multiple data admin sessions?
+                               return getSystemSession();
+                       }
+                       throw new IllegalStateException("Caller must be authenticated");
+               }
+
                CmsSession cmsSession = CurrentUser.getCmsSession();
                CmsContentSession contentSession = userSessions.get(cmsSession);
                if (contentSession == null) {
index f811642c7f7f4971b379ac2aca0c709c104b61cb..13494dd0a8588825b23c386fa926471bbe5543dc 100644 (file)
@@ -1,8 +1,8 @@
 package org.argeo.cms.acr;
 
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
@@ -34,7 +34,7 @@ class CmsContentSession implements ProvidedSession {
 
        private CompletableFuture<ContentSession> edition;
 
-       private Set<ContentProvider> modifiedProviders = new TreeSet<>();
+       private Set<ContentProvider> modifiedProviders = new HashSet<>();
 
        private Content sessionRunDir;
 
index 5ea79662a387341f5d902c58f0fdc5c95de74f3b..17144cfdb660cfceaf84c54016b46b6f44c1ac8e 100644 (file)
@@ -6,12 +6,18 @@ import java.util.List;
 import java.util.StringJoiner;
 import java.util.function.BiConsumer;
 
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
 import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.cms.CmsAuth;
 import org.argeo.cms.CmsUserManager;
 import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.CurrentSubject;
 import org.osgi.service.useradmin.Role;
 
 /** Utilities and routines around {@link Content}. */
@@ -113,11 +119,6 @@ public class ContentUtils {
                        throw new IllegalArgumentException("Path " + path + " contains //");
        }
 
-       /** Singleton. */
-       private ContentUtils() {
-
-       }
-
        public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
                UserDirectory userDirectory = userManager.getDirectory(role);
                String path = CmsContentRepository.DIRECTORY_BASE + SLASH + userDirectory.getName() + SLASH
@@ -126,4 +127,48 @@ public class ContentUtils {
                return content;
        }
 
+       /*
+        * CONSUMER UTILS
+        */
+
+       public static Content createCollections(ContentSession session, String path) {
+               if (session.exists(path)) {
+                       Content content = session.get(path);
+                       if (!content.isContentClass(CrName.collection.qName())) {
+                               throw new IllegalStateException("Content " + path + " already exists, but is not a collection");
+                       } else {
+                               return content;
+                       }
+               } else {
+                       String[] parentPath = getParentPath(path);
+                       Content parent = createCollections(session, parentPath[0]);
+                       Content content = parent.add(parentPath[1], CrName.collection.qName());
+                       return content;
+               }
+       }
+
+       public static ContentSession openDataAdminSession(ContentRepository repository) {
+               LoginContext loginContext;
+               try {
+                       loginContext = CmsAuth.DATA_ADMIN.newLoginContext();
+                       loginContext.login();
+               } catch (LoginException e1) {
+                       throw new RuntimeException("Could not login as data admin", e1);
+               } finally {
+               }
+
+               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(ContentUtils.class.getClassLoader());
+                       return CurrentSubject.callAs(loginContext.getSubject(), () -> repository.get());
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentCl);
+               }
+       }
+
+       /** Singleton. */
+       private ContentUtils() {
+
+       }
+
 }
index 69b76ddc641f859bafb0ca2083b7ab9ccc5b1805..8cb90893637b91ebd63dbb2b8db441c389384d46 100644 (file)
@@ -52,8 +52,13 @@ class MountManager {
                if (entry == null)
                        throw new IllegalArgumentException("No entry provider found for " + path);
                String mountPath = entry.getKey();
-               if (!path.startsWith(mountPath))
-                       throw new IllegalArgumentException("Path " + path + " doesn't have a content provider");
+               if (!path.startsWith(mountPath)) {
+                       // FIXME make it more robust and find when there is no content provider
+                       String[] parent = ContentUtils.getParentPath(path);
+                       return findContentProvider(parent[0]);
+                       // throw new IllegalArgumentException("Path " + path + " doesn't have a content
+                       // provider");
+               }
                ContentProvider contentProvider = entry.getValue();
                assert mountPath.equals(contentProvider.getMountPath());
                return contentProvider;
index 6cfb2e5e9cbc2a74736c9a231e488ad99e164ac0..23d6042d7ac23c53a8d7a797c74cd8274e07c4f0 100644 (file)
@@ -65,7 +65,7 @@ class TypesManager {
        private XSModel xsModel;
        private SortedMap<QName, Map<QName, CrAttributeType>> types;
 
-       private boolean validating = true;
+       private boolean validating = false;
 
        private final static boolean limited = false;
 
@@ -99,6 +99,7 @@ class TypesManager {
                if (xsdSystemId != null) {
                        sources.add(new StreamSource(xsdSystemId));
                        reload();
+                       log.debug(() -> "Registered types " + namespace + " from " + xsdSystemId);
                }
        }
 
@@ -452,7 +453,8 @@ class TypesManager {
                try {
                        validator.validate(source);
                } catch (SAXException e) {
-                       throw new IllegalArgumentException("Provided source is not valid", e);
+                       log.error(source + " is not valid ", e);
+                       // throw new IllegalArgumentException("Provided source is not valid", e);
                }
        }
 
index 76dc9da71d65bf95053679e2d99cf6e969623f69..77c2f7a7585fe778834ec9735635b749905581d1 100644 (file)
@@ -8,6 +8,7 @@ import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.time.Instant;
@@ -20,6 +21,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 import javax.xml.namespace.QName;
 
@@ -322,4 +324,24 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                return provider;
        }
 
+       /*
+        * READ / WRITE
+        */
+       public <A> CompletableFuture<A> write(Class<A> clss) {
+               if (isContentClass(CrName.collection.qName())) {
+                       throw new IllegalStateException("Cannot directly write to a collection");
+               }
+               if (InputStream.class.isAssignableFrom(clss)) {
+                       CompletableFuture<InputStream> res = new CompletableFuture<>();
+                       res.thenAccept((in) -> {
+                               try {
+                                       Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot write to " + path, e);
+                               }
+                       });
+                       return (CompletableFuture<A>) res;
+               }
+               return super.write(clss);
+       }
 }
index 1b5741431145663d55e871141c0191287fa4a1ce..59a4d8deb5616f12c6b1b24c61406726f3f121b2 100644 (file)
@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
 import org.argeo.api.acr.ContentResourceException;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
@@ -111,15 +112,15 @@ public class FsContentProvider implements ContentProvider {
                return new FsContent(session, this, rootPath.resolve(relativePath));
        }
 
-       /*
-        * NAMESPACE CONTEXT
-        */
-
        @Override
        public boolean exists(ProvidedSession session, String relativePath) {
                return Files.exists(rootPath.resolve(relativePath));
        }
 
+       /*
+        * NAMESPACE CONTEXT
+        */
+
        @Override
        public String getNamespaceURI(String prefix) {
                return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), prefix);
@@ -127,8 +128,19 @@ public class FsContentProvider implements ContentProvider {
 
        @Override
        public Iterator<String> getPrefixes(String namespaceURI) {
-               return NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream().filter(e -> e.getValue().equals(ns))
-                               .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), namespaceURI);
+               Iterator<String> res = NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream()
+                               .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()),
+                               namespaceURI);
+               if (!res.hasNext()) {
+                       String prefix = RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI);
+                       if (prefix != null) {
+                               registerPrefix(prefix, namespaceURI);
+                               return getPrefixes(namespaceURI);
+                       } else {
+                               throw new IllegalArgumentException("Unknown namespace " + namespaceURI);
+                       }
+               }
+               return res;
        }
 
 }
index d2b5188f5fd5f88f93599f4876c72b251f7a0423..30ecd8e82b649be7251473c204062937f5a3af8c 100644 (file)
@@ -1,7 +1,6 @@
 package org.argeo.cms.acr.xml;
 
 import java.nio.CharBuffer;
-import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -13,16 +12,22 @@ import java.util.concurrent.CompletableFuture;
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.namespace.QName;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.dom.DOMResult;
+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;
 import org.argeo.cms.acr.ContentUtils;
 import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
@@ -257,6 +262,9 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                        String textContent = element.getTextContent();
                        CharBuffer buf = CharBuffer.wrap(textContent);
                        return (A) buf;
+               } else if (Source.class.isAssignableFrom(clss)) {
+                       DOMSource source = new DOMSource(element);
+                       return (A) source;
                }
                return super.adapt(clss);
        }
@@ -270,6 +278,42 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                                element.setTextContent(s);
                        });
                        return (CompletableFuture<A>) res;
+               } else if (Source.class.isAssignableFrom(clss)) {
+                       CompletableFuture<Source> res = new CompletableFuture<>();
+                       res.thenAccept((source) -> {
+                               try {
+                                       Transformer transformer = provider.getTransformerFactory().newTransformer();
+                                       DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment();
+                                       DOMResult result = new DOMResult(documentFragment);
+                                       transformer.transform(source, result);
+                                       // Node parentNode = element.getParentNode();
+                                       Element resultElement = (Element) documentFragment.getFirstChild();
+                                       QName resultName = toQName(resultElement);
+                                       if (!resultName.equals(getName()))
+                                               throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName());
+
+                                       // attributes
+                                       NamedNodeMap attrs = resultElement.getAttributes();
+                                       for (int i = 0; i < attrs.getLength(); i++) {
+                                               Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true);
+                                               element.getAttributes().setNamedItem(attr2);
+                                       }
+
+                                       // Move all the children
+                                       while (element.hasChildNodes()) {
+                                               element.removeChild(element.getFirstChild());
+                                       }
+                                       while (resultElement.hasChildNodes()) {
+                                               element.appendChild(resultElement.getFirstChild());
+                                       }
+//                                     parentNode.replaceChild(resultNode, element);
+//                                     element = (Element)resultNode;
+                                       
+                               } catch (DOMException | TransformerException e) {
+                                       throw new RuntimeException("Cannot write to element", e);
+                               }
+                       });
+                       return (CompletableFuture<A>) res;
                }
                return super.write(clss);
        }
index d6e246df53fb9282c9171cc8e2fd977b12a07b4b..f76d38ce07a338401452834a3541400892c5cccf 100644 (file)
@@ -8,6 +8,7 @@ import java.util.Iterator;
 import java.util.List;
 
 import javax.xml.namespace.NamespaceContext;
+import javax.xml.transform.TransformerFactory;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
@@ -32,12 +33,17 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
        // TODO centralise in some executor?
        private final ThreadLocal<XPath> xPath;
 
+       private TransformerFactory transformerFactory;
+
        private String mountPath;
 
        public DomContentProvider(String mountPath, Document document) {
                this.mountPath = mountPath;
                this.document = document;
                this.document.normalizeDocument();
+
+               transformerFactory = TransformerFactory.newInstance();
+
                XPathFactory xPathFactory = XPathFactory.newInstance();
                xPath = new ThreadLocal<>() {
 
@@ -145,4 +151,8 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
                return Collections.unmodifiableList(res).iterator();
        }
 
+       TransformerFactory getTransformerFactory() {
+               return transformerFactory;
+       }
+
 }
index ea1046be9e00c2ed164f03a6fbcbed7d0da8dcf2..d4f402853b2174bdb1e568f906f70e5dd9b57e5f 100644 (file)
@@ -45,4 +45,5 @@ public class DataAdminLoginModule implements LoginModule {
                subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
                return true;
        }
+
 }