Start supporting mounting of XML with FS.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 1 Jun 2022 08:34:07 +0000 (10:34 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 1 Jun 2022 08:34:07 +0000 (10:34 +0200)
18 files changed:
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrContentTreeView.java
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.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/acr/xml/DomUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java

index c6d7380203413ecab495b9d2f9941c3d7b74b9eb..ddf409e0b8d01609ddf75234e6e3af32cb01eccf 100644 (file)
@@ -116,7 +116,7 @@ public class AcrContentTreeView extends Composite {
                shell.setText(basePath.toString());
                shell.setLayout(new FillLayout());
 
-               FsContentProvider contentSession = new FsContentProvider(basePath, true);
+               FsContentProvider contentSession = new FsContentProvider(null, basePath, true);
 //             GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
 
                shell.setSize(shell.computeSize(800, 600));
index 9f78577b50b0095767c36cb44bd0557d570bc875..134f68162fcdfa5525d14bc0793c1dd9c4a2cc0c 100644 (file)
@@ -27,6 +27,7 @@ import javax.xml.transform.Source;
 import javax.xml.transform.stream.StreamSource;
 
 import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentUtils;
 import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.AbstractContent;
 import org.argeo.api.acr.spi.ProvidedSession;
@@ -55,7 +56,9 @@ public class JcrContent extends AbstractContent {
        public QName getName() {
                String name = Jcr.getName(getJcrNode());
                if (name.equals("")) {// root
-                       name = Jcr.getWorkspaceName(getJcrNode());
+                       String mountPath = provider.getMountPath();
+                       name = ContentUtils.getParentPath(mountPath)[1];
+                       // name = Jcr.getWorkspaceName(getJcrNode());
                }
                return NamespaceUtils.parsePrefixedName(provider, name);
        }
index a7005ce23c826b464c7716a5277a6edf1e2e2e95..f7bfd0949f422b5ce359111347c6e8723de1ca11 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Objects;
 
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
@@ -15,6 +16,7 @@ import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentUtils;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.CmsContentRepository;
 import org.argeo.cms.jcr.CmsJcrUtils;
 import org.argeo.jcr.JcrException;
 import org.argeo.jcr.JcrUtils;
@@ -24,9 +26,13 @@ public class JcrContentProvider implements ContentProvider, NamespaceContext {
        private Repository jcrRepository;
        private Session adminSession;
 
+       private String mountPath;
+
        private Map<ProvidedSession, JcrSessionAdapter> sessionAdapters = Collections.synchronizedMap(new HashMap<>());
 
-       public void start() {
+       public void start(Map<String, String> properties) {
+               mountPath = properties.get(CmsContentRepository.ACR_MOUNT_PATH_PROPERTY);
+               Objects.requireNonNull(mountPath);
                adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
        }
 
@@ -59,6 +65,11 @@ public class JcrContentProvider implements ContentProvider, NamespaceContext {
                return jcrSession;
        }
 
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
        /*
         * NAMESPACE CONTEXT
         */
index aeea27ef23ff272b2bb0fb0bf4a95c8c65336e1b..c2202b0fa31e14ad7db87a0f104390beaa792a7f 100644 (file)
@@ -5,6 +5,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
 
 import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
@@ -50,6 +51,7 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
 
        <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
 
+       @SuppressWarnings("unchecked")
        default <A> List<A> getMultiple(QName key) {
                Class<A> type;
                try {
@@ -70,6 +72,13 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
        /*
         * CONTENT OPERATIONS
         */
+//     default CompletionStage<Content> edit(Consumer<Content> work) {
+//             return CompletableFuture.supplyAsync(() -> {
+//                     work.accept(this);
+//                     return this;
+//             }).minimalCompletionStage();
+//     }
+
        Content add(QName name, QName... classes);
 
        default Content add(String name, QName... classes) {
@@ -84,11 +93,15 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
         * DEFAULT METHODS
         */
        default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
-               throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
+               throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
+       }
+
+       default <C extends Closeable> C open(Class<C> clss) throws IOException {
+               throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName());
        }
 
-       default <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
-               throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
+       default <A> CompletableFuture<A> write(Class<A> clss) {
+               throw new UnsupportedOperationException("Cannot write content " + this + " as " + clss.getName());
        }
 
        /*
index e3a09f60367ea6cd5f4cf27fddc106f3a3e12523..3adde724bc9bbf5d5d16a0bbcd2952ea0035e009 100644 (file)
@@ -1,6 +1,8 @@
 package org.argeo.api.acr;
 
 import java.util.Locale;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
 
 import javax.security.auth.Subject;
 import javax.xml.namespace.NamespaceContext;
@@ -12,4 +14,5 @@ public interface ContentSession extends NamespaceContext {
 
        Content get(String path);
 
+       CompletionStage<ContentSession> edit(Consumer<ContentSession> work);
 }
index 9d2215f6579436e14f4d5dc3d9814d51419a5184..c07a43b6e3ceb1c5d08c41ca7bb811c1a02c8379 100644 (file)
@@ -10,6 +10,8 @@ public interface ContentProvider extends NamespaceContext {
 
        Content get(ProvidedSession session, String mountPath, String relativePath);
 
+       String getMountPath();
+
        /*
         * NAMESPACE CONTEXT
         */
index d9fc781d0317878d1ba0be628e080e9ac112c30d..d483e7fcc7f9e91047e0739258f11cf4064621df 100644 (file)
@@ -6,4 +6,8 @@ public interface ProvidedContent extends Content {
        ProvidedSession getSession();
 
        ContentProvider getProvider();
+
+       default ProvidedContent getMountPoint(String relativePath) {
+               throw new UnsupportedOperationException("This content doe not support mount");
+       }
 }
index cde027baf3a3c954d7f1c519763a27d7bd27bb6b..dcf49d0d72e95da0bf73260559edf59bad621ea2 100644 (file)
@@ -1,7 +1,15 @@
 package org.argeo.api.acr.spi;
 
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentRepository;
 
 public interface ProvidedRepository extends ContentRepository {
-       public void registerTypes(String prefix, String namespaceURI, String schemaSystemId);
+       void registerTypes(String prefix, String namespaceURI, String schemaSystemId);
+
+       ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types);
+
+
+       boolean shouldMount(QName... types);
 }
index 60f64def8dd77ad128e2b7931c6624f64b2ae161..7c6912c4e7f6d4baf4ff1297af140f3a78494b4c 100644 (file)
@@ -3,8 +3,7 @@ package org.argeo.api.acr.spi;
 import java.util.Iterator;
 import java.util.concurrent.CompletionStage;
 
-import javax.xml.namespace.NamespaceContext;
-
+import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
 
 public interface ProvidedSession extends ContentSession {
@@ -12,6 +11,9 @@ public interface ProvidedSession extends ContentSession {
 
        CompletionStage<ProvidedSession> onClose();
 
+       Content getMountPoint(String path);
+
+       boolean isEditing();
        /*
         * NAMESPACE CONTEXT
         */
index 50438e89a094b4cf7411c3bb86a7358eb93a5b30..d91fb16fec9ae8936fe28e72c6e889b88d27bf88 100644 (file)
@@ -1,8 +1,8 @@
 package org.argeo.cms.acr;
 
 import java.io.IOException;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -11,24 +11,25 @@ import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
 import java.util.NavigableMap;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
-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.OutputKeys;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
+import javax.xml.validation.Validator;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
@@ -36,21 +37,20 @@ import org.argeo.api.acr.ContentUtils;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedRepository;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.cms.acr.xml.DomContentProvider;
+import org.argeo.cms.acr.xml.DomUtils;
 import org.argeo.cms.auth.CurrentUser;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
 
 /**
  * Base implementation of a {@link ProvidedRepository} integrated with a CMS.
@@ -70,11 +70,19 @@ public class CmsContentRepository implements ProvidedRepository {
 
        private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
 
+       // utilities
+       private TransformerFactory transformerFactory = TransformerFactory.newInstance();
+
+       public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path";
+
        public CmsContentRepository() {
                contentTypesManager = new ContentTypesManager();
                contentTypesManager.init();
-               contentTypesManager.listTypes();
-               
+               Set<QName> types = contentTypesManager.listTypes();
+               for (QName type : types) {
+                       log.debug(type);
+               }
+
                systemSession = newSystemSession();
        }
 
@@ -152,30 +160,12 @@ public class CmsContentRepository implements ProvidedRepository {
         */
        public void initRootContentProvider(Path path) {
                try {
-                       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                       factory.setNamespaceAware(true);
-                       factory.setXIncludeAware(true);
-                       // factory.setSchema(schema);
-
-                       factory.setSchema(contentTypesManager.getSchema());
-
-                       DocumentBuilder dBuilder = factory.newDocumentBuilder();
-                       dBuilder.setErrorHandler(new ErrorHandler() {
-
-                               @Override
-                               public void warning(SAXParseException exception) throws SAXException {
-                               }
-
-                               @Override
-                               public void fatalError(SAXParseException exception) throws SAXException {
-                               }
-
-                               @Override
-                               public void error(SAXParseException exception) throws SAXException {
-                                       log.error(exception);
-
-                               }
-                       });
+//                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+//                     factory.setNamespaceAware(true);
+//                     factory.setXIncludeAware(true);
+//                     factory.setSchema(contentTypesManager.getSchema());
+//
+                       DocumentBuilder dBuilder = contentTypesManager.newDocumentBuilder();
 
                        Document document;
 //                     if (path != null && Files.exists(path)) {
@@ -188,33 +178,93 @@ public class CmsContentRepository implements ProvidedRepository {
                        Element root = document.createElementNS(CrName.CR_NAMESPACE_URI, CrName.ROOT.get().toPrefixedString());
 
                        for (String prefix : contentTypesManager.getPrefixes().keySet()) {
-                               root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
-                                               contentTypesManager.getPrefixes().get(prefix));
+//                             root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+//                                             contentTypesManager.getPrefixes().get(prefix));
+                               DomUtils.addNamespace(root, prefix, contentTypesManager.getPrefixes().get(prefix));
                        }
 
                        document.appendChild(root);
 
                        // write it
                        if (path != null) {
-                               TransformerFactory transformerFactory = TransformerFactory.newInstance();
-                               Transformer transformer = transformerFactory.newTransformer();
-                               DOMSource source = new DOMSource(document);
-                               try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
-                                       StreamResult result = new StreamResult(writer);
-                                       transformer.transform(source, result);
+                               try (OutputStream out = Files.newOutputStream(path)) {
+                                       writeDom(document, out);
                                }
                        }
 //                     }
 
-                       DomContentProvider contentProvider = new DomContentProvider(document);
+                       DomContentProvider contentProvider = new DomContentProvider(null, document);
                        addProvider("/", contentProvider);
-               } catch (DOMException | ParserConfigurationException | IOException | TransformerFactoryConfigurationError
-                               | TransformerException e) {
+               } catch (DOMException | IOException e) {
                        throw new IllegalStateException("Cannot init ACR root " + path, e);
                }
 
        }
 
+       public void writeDom(Document document, OutputStream out) throws IOException {
+               try {
+                       Transformer transformer = transformerFactory.newTransformer();
+                       transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+                       DOMSource source = new DOMSource(document);
+                       contentTypesManager.validate(source);
+                       StreamResult result = new StreamResult(out);
+                       transformer.transform(source, result);
+               } catch (TransformerException e) {
+                       throw new IOException("Cannot write dom", e);
+               }
+       }
+
+       /*
+        * MOUNT MANAGEMENT
+        */
+
+       @Override
+       public ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types) {
+               String mountPath = mountPoint.getPath();
+               if (partitions.containsKey(mountPath))
+                       // TODO check consistency with types
+                       return partitions.get(mountPath);
+               DocumentBuilder dBuilder = contentTypesManager.newDocumentBuilder();
+               Document document;
+               if (initialize) {
+                       QName firstType = types[0];
+                       document = dBuilder.newDocument();
+                       String prefix = ((ProvidedContent) mountPoint).getSession().getPrefix(firstType.getNamespaceURI());
+                       Element root = document.createElementNS(firstType.getNamespaceURI(),
+                                       prefix + ":" + firstType.getLocalPart());
+                       DomUtils.addNamespace(root, prefix, firstType.getNamespaceURI());
+                       document.appendChild(root);
+//                     try (OutputStream out = mountPoint.open(OutputStream.class)) {
+//                             writeDom(document, out);
+//                     } catch (IOException e) {
+//                             throw new IllegalStateException("Cannot write mount from " + mountPoint, e);
+//                     }
+               } else {
+                       try (InputStream in = mountPoint.open(InputStream.class)) {
+                               document = dBuilder.parse(in);
+                               // TODO check consistency with types
+                       } catch (IOException | SAXException e) {
+                               throw new IllegalStateException("Cannot load mount from " + mountPoint, e);
+                       }
+               }
+               DomContentProvider contentProvider = new DomContentProvider(mountPath, document);
+               partitions.put(mountPath, contentProvider);
+               return contentProvider;
+       }
+
+       @Override
+       public boolean shouldMount(QName... types) {
+               if (types.length == 0)
+                       throw new IllegalArgumentException("Types must be provided");
+               QName firstType = types[0];
+               Set<QName> registeredTypes = contentTypesManager.listTypes();
+               if (registeredTypes.contains(firstType))
+                       return true;
+               return false;
+       }
+
        /*
         * NAMESPACE CONTEXT
         */
@@ -229,6 +279,8 @@ public class CmsContentRepository implements ProvidedRepository {
 
                private CompletableFuture<ProvidedSession> closed = new CompletableFuture<>();
 
+               private CompletableFuture<ContentSession> edition;
+
                public CmsContentSession(Subject subject, Locale locale) {
                        this.subject = subject;
                        this.locale = locale;
@@ -271,6 +323,17 @@ public class CmsContentRepository implements ProvidedRepository {
                        return CmsContentRepository.this;
                }
 
+               /*
+                * MOUNT MANAGEMENT
+                */
+               @Override
+               public Content getMountPoint(String path) {
+                       String[] parent = ContentUtils.getParentPath(path);
+                       ProvidedContent mountParent = (ProvidedContent) get(parent[0]);
+//                     Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
+                       return mountParent.getMountPoint(parent[1]);
+               }
+
                /*
                 * NAMESPACE CONTEXT
                 */
@@ -288,6 +351,28 @@ public class CmsContentRepository implements ProvidedRepository {
                                        namespaceURI);
                }
 
+               @Override
+               public CompletionStage<ContentSession> edit(Consumer<ContentSession> work) {
+                       edition = CompletableFuture.supplyAsync(() -> {
+                               work.accept(this);
+                               return this;
+                       }).thenApply((s) -> {
+                               // TODO optimise
+                               for (ContentProvider provider : partitions.values()) {
+                                       if (provider instanceof DomContentProvider) {
+                                               ((DomContentProvider) provider).persist(s);
+                                       }
+                               }
+                               return s;
+                       });
+                       return edition.minimalCompletionStage();
+               }
+
+               @Override
+               public boolean isEditing() {
+                       return edition != null && !edition.isDone();
+               }
+
 //             @Override
 //             public String findNamespace(String prefix) {
 //                     return prefixes.get(prefix);
index 23f2d90018beb9668e0be2e8ade946fd55429314..48093bee034b9bdedf2f5f1f633f0419e60ce90f 100644 (file)
@@ -1,14 +1,24 @@
 package org.argeo.cms.acr;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 
+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.stream.StreamSource;
 import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
 
 import org.apache.xerces.impl.xs.XSImplementationImpl;
 import org.apache.xerces.impl.xs.util.StringListImpl;
@@ -24,20 +34,40 @@ import org.apache.xerces.xs.XSNamedMap;
 import org.apache.xerces.xs.XSTypeDefinition;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.cms.CmsLog;
+import org.xml.sax.ErrorHandler;
 import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
 
 public class ContentTypesManager {
        private final static CmsLog log = CmsLog.getLog(ContentTypesManager.class);
        private Map<String, String> prefixes = new TreeMap<>();
 
+       // immutable factories
+       private SchemaFactory schemaFactory;
+
+       /** Schema sources. */
        private List<Source> sources = new ArrayList<>();
 
-       private SchemaFactory schemaFactory;
+       // cached
        private Schema schema;
+       DocumentBuilderFactory documentBuilderFactory;
+       private XSModel xsModel;
+       private NavigableSet<QName> types;
+
+       private boolean validating = true;
 
        public ContentTypesManager() {
                schemaFactory = SchemaFactory.newDefaultInstance();
 
+               // types
+               types = new TreeSet<>((qn1, qn2) -> {
+                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+                       } else {
+                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+                       }
+               });
+
        }
 
        public synchronized void init() {
@@ -46,53 +76,98 @@ public class ContentTypesManager {
                prefixes.put("owner", CrName.CR_NAMESPACE_URI);
                prefixes.put("posix", CrName.CR_NAMESPACE_URI);
 
-               try {
-                       for (CmsContentTypes cs : CmsContentTypes.values()) {
-                               StreamSource source = new StreamSource(cs.getResource().toExternalForm());
-                               sources.add(source);
-                               if (prefixes.containsKey(cs.getDefaultPrefix()))
-                                       throw new IllegalStateException("Prefix " + cs.getDefaultPrefix() + " is already mapped with "
-                                                       + prefixes.get(cs.getDefaultPrefix()));
-                               prefixes.put(cs.getDefaultPrefix(), cs.getNamespace());
-                       }
-
-                       schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
-               } catch (SAXException e) {
-                       throw new IllegalStateException("Cannot initialise types", e);
+               for (CmsContentTypes cs : CmsContentTypes.values()) {
+                       StreamSource source = new StreamSource(cs.getResource().toExternalForm());
+                       sources.add(source);
+                       if (prefixes.containsKey(cs.getDefaultPrefix()))
+                               throw new IllegalStateException("Prefix " + cs.getDefaultPrefix() + " is already mapped with "
+                                               + prefixes.get(cs.getDefaultPrefix()));
+                       prefixes.put(cs.getDefaultPrefix(), cs.getNamespace());
                }
 
+               reload();
        }
 
        public synchronized void registerTypes(String defaultPrefix, String namespace, String xsdSystemId) {
-               try {
-                       if (prefixes.containsKey(defaultPrefix))
-                               throw new IllegalStateException(
-                                               "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
-                       prefixes.put(defaultPrefix, namespace);
+               if (prefixes.containsKey(defaultPrefix))
+                       throw new IllegalStateException(
+                                       "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
+               prefixes.put(defaultPrefix, namespace);
 
-                       sources.add(new StreamSource(xsdSystemId));
-                       schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
-               } catch (SAXException e) {
-                       throw new IllegalStateException("Cannot initialise types " + namespace + " based on " + xsdSystemId, e);
-               }
+               sources.add(new StreamSource(xsdSystemId));
+               reload();
+       }
 
+       public Set<QName> listTypes() {
+// TODO cache it?
+               return types;
        }
 
-       public void listTypes() {
+       private synchronized void reload() {
                try {
-                       // Find an XMLSchema loader instance
+                       // schema
+                       schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()]));
+
+                       // document builder factory
+                       documentBuilderFactory = DocumentBuilderFactory.newInstance();
+                       documentBuilderFactory.setNamespaceAware(true);
+                       documentBuilderFactory.setXIncludeAware(true);
+                       documentBuilderFactory.setSchema(getSchema());
+                       documentBuilderFactory.setValidating(validating);
+
+                       // XS model
+                       // TODO use JVM implementation?
 //                     DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
 //                     XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader");
-                       XSImplementation implementation = new XSImplementationImpl();
-                       XSLoader loader = implementation.createXSLoader(null);
-
-                       // Load the XML Schema
+                       XSImplementation xsImplementation = new XSImplementationImpl();
+                       XSLoader xsLoader = xsImplementation.createXSLoader(null);
                        List<String> systemIds = new ArrayList<>();
                        for (Source source : sources) {
                                systemIds.add(source.getSystemId());
                        }
                        StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
-                       XSModel xsModel = loader.loadURIList(sl);
+                       xsModel = xsLoader.loadURIList(sl);
+
+                       // types
+                       XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+                       for (int i = 0; i < map.getLength(); i++) {
+                               XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+                               QName type = new QName(eDec.getNamespace(), eDec.getName());
+                               types.add(type);
+                       }
+               } catch (XSException | SAXException e) {
+                       throw new IllegalStateException("Cannot relaod types");
+               }
+       }
+
+       public DocumentBuilder newDocumentBuilder() {
+               try {
+                       DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
+                       dBuilder.setErrorHandler(new ErrorHandler() {
+
+                               @Override
+                               public void warning(SAXParseException exception) throws SAXException {
+                                       log.warn(exception);
+                               }
+
+                               @Override
+                               public void fatalError(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+                               }
+
+                               @Override
+                               public void error(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+                               }
+                       });
+                       return dBuilder;
+               } catch (ParserConfigurationException e) {
+                       throw new IllegalStateException("Cannot create document builder", e);
+               }
+       }
+
+       public void printTypes() {
+               try {
 
                        // Convert top level complex type definitions to node types
                        log.debug("\n## TYPES");
@@ -123,6 +198,20 @@ public class ContentTypesManager {
 
        }
 
+       public void validate(Source source) throws IOException {
+               if (!validating)
+                       return;
+               Validator validator;
+               synchronized (this) {
+                       validator = schema.newValidator();
+               }
+               try {
+                       validator.validate(source);
+               } catch (SAXException e) {
+                       throw new IllegalArgumentException("Provided source is not valid", e);
+               }
+       }
+
        public Map<String, String> getPrefixes() {
                return prefixes;
        }
index b8bbddb5016d13c2b4136e8e525c9c6858958409..a2cc52c42850f54bd2d7e25258b3cab866f10993 100644 (file)
@@ -1,6 +1,9 @@
 package org.argeo.cms.acr.fs;
 
+import java.io.Closeable;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -21,9 +24,11 @@ import javax.xml.namespace.QName;
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
 import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.ContentUtils;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.util.FsUtils;
@@ -58,9 +63,15 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                this.path = path;
                this.isRoot = contentProvider.isRoot(path);
                // TODO check file names with ':' ?
-               if (isRoot)
-                       this.name = CrName.ROOT.get();
-               else {
+               if (isRoot) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath != null) {
+                               Content mountPoint = session.getMountPoint(mountPath);
+                               this.name = mountPoint.getName();
+                       } else {
+                               this.name = CrName.ROOT.get();
+                       }
+               } else {
                        QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
                        this.name = new ContentName(providerName, session);
                }
@@ -169,7 +180,19 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        public Iterator<Content> iterator() {
                if (Files.isDirectory(path)) {
                        try {
-                               return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
+                               return Files.list(path).map((p) -> {
+                                       FsContent fsContent = new FsContent(this, p);
+                                       Optional<String> isMount = fsContent.get(CrName.MOUNT.get(), String.class);
+                                       if (isMount.orElse("false").equals("true")) {
+                                               QName[] classes = null;
+                                               ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent,
+                                                               false, classes);
+                                               Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
+                                               return mountedContent;
+                                       } else {
+                                               return (Content) fsContent;
+                                       }
+                               }).iterator();
                        } catch (IOException e) {
                                throw new ContentResourceException("Cannot list " + path, e);
                        }
@@ -180,6 +203,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public Content add(QName name, QName... classes) {
+               FsContent fsContent;
                try {
                        Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
                        if (ContentName.contains(classes, CrName.COLLECTION.get()))
@@ -190,10 +214,20 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 //             for(ContentClass clss:classes) {
 //                     Files.setAttribute(newPath, name, newPath, null)
 //             }
-                       return new FsContent(this, newPath);
+                       fsContent = new FsContent(this, newPath);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot create new content", e);
                }
+
+               if (session.getRepository().shouldMount(classes)) {
+                       ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent, true, classes);
+                       Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
+                       fsContent.put(CrName.MOUNT.get(), "true");
+                       return mountedContent;
+
+               } else {
+                       return fsContent;
+               }
        }
 
        @Override
@@ -203,11 +237,40 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public Content getParent() {
-               if (isRoot)
-                       return null;// TODO deal with mounts
+               if (isRoot) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath == null)
+                               return null;
+                       String[] parent = ContentUtils.getParentPath(mountPath);
+                       return session.get(parent[0]);
+               }
                return new FsContent(this, path.getParent());
        }
 
+       @Override
+       public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
+               if (InputStream.class.isAssignableFrom(clss)) {
+                       if (Files.isDirectory(path))
+                               throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
+                       return (C) Files.newInputStream(path);
+               } else if (OutputStream.class.isAssignableFrom(clss)) {
+                       if (Files.isDirectory(path))
+                               throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
+                       return (C) Files.newOutputStream(path);
+               }
+               return super.open(clss);
+       }
+
+       /*
+        * MOUNT MANAGEMENT
+        */
+       @Override
+       public ProvidedContent getMountPoint(String relativePath) {
+               Path childPath = path.resolve(relativePath);
+               // TODO check that it is a mount
+               return new FsContent(this, childPath);
+       }
+
        /*
         * ACCESSORS
         */
index 37401a29c7f894117a2a0240910ee0cd3c5fe595..3445733e18a13ff1727c20fb139ed82c9ba49c5f 100644 (file)
@@ -23,12 +23,14 @@ import org.argeo.api.acr.spi.ProvidedSession;
 public class FsContentProvider implements ContentProvider {
        final static String XMLNS_ = "xmlns:";
 
+       private final String mountPath;
        private final Path rootPath;
        private final boolean isRoot;
 
        private NavigableMap<String, String> prefixes = new TreeMap<>();
 
-       public FsContentProvider(Path rootPath, boolean isRoot) {
+       public FsContentProvider(String mountPath, Path rootPath, boolean isRoot) {
+               this.mountPath = mountPath;
                this.rootPath = rootPath;
                this.isRoot = isRoot;
                initNamespaces();
@@ -81,6 +83,12 @@ public class FsContentProvider implements ContentProvider {
                }
 
        }
+       
+       
+@Override
+       public String getMountPath() {
+               return mountPath;
+       }
 
        boolean isRoot(Path path) {
                try {
index 9e370db0c07ebfd552186300c59b677ff87952c3..9a1a58c55a7ef3162739d8c7fed32bceed5d0e01 100644 (file)
@@ -1,9 +1,12 @@
 package org.argeo.cms.acr.xml;
 
+import java.nio.CharBuffer;
+import java.nio.file.Path;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
@@ -11,9 +14,12 @@ import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentUtils;
+import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.AbstractContent;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.fs.FsContent;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -43,6 +49,13 @@ public class DomContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public QName getName() {
+               if (element.getParentNode() == null) {// root
+                       String mountPath = provider.getMountPath();
+                       if (mountPath != null) {
+                               Content mountPoint = session.getMountPoint(mountPath);
+                               return mountPoint.getName();
+                       }
+               }
                return toQName(this.element);
        }
 
@@ -173,14 +186,19 @@ public class DomContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public Content getParent() {
-               Node parent = element.getParentNode();
-               if (parent == null)
-                       return null;
-               if (parent instanceof Document)
+               Node parentNode = element.getParentNode();
+               if (parentNode == null) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath == null)
+                               return null;
+                       String[] parent = ContentUtils.getParentPath(mountPath);
+                       return session.get(parent[0]);
+               }
+               if (parentNode instanceof Document)
                        return null;
-               if (!(parent instanceof Element))
+               if (!(parentNode instanceof Element))
                        throw new IllegalStateException("Parent is not an element");
-               return new DomContent(this, (Element) parent);
+               return new DomContent(this, (Element) parentNode);
        }
 
        @Override
@@ -211,6 +229,36 @@ public class DomContent extends AbstractContent implements ProvidedContent {
 
        }
 
+       @Override
+       public <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+               if (CharBuffer.class.isAssignableFrom(clss)) {
+                       String textContent = element.getTextContent();
+                       CharBuffer buf = CharBuffer.wrap(textContent);
+                       return (A) buf;
+               }
+               return super.adapt(clss);
+       }
+
+       public <A> CompletableFuture<A> write(Class<A> clss) {
+               if (String.class.isAssignableFrom(clss)) {
+                       CompletableFuture<String> res = new CompletableFuture<>();
+                       res.thenAccept((s) -> element.setTextContent(s));// .thenRun(() -> provider.persist(session));
+                       return (CompletableFuture<A>) res;
+               }
+               return super.write(clss);
+       }
+
+       /*
+        * MOUNT MANAGEMENT
+        */
+       @Override
+       public ProvidedContent getMountPoint(String relativePath) {
+               // FIXME use qualified names
+               Element childElement = (Element) element.getElementsByTagName(relativePath).item(0);
+               // TODO check that it is a mount
+               return new DomContent(this, childElement);
+       }
+
        public ProvidedSession getSession() {
                return session;
        }
index 4db49343386902e40bdd2e15f7a61bf2996b823a..8caac1ad29a2629e11e09b7c72dc2857ead361f2 100644 (file)
@@ -1,5 +1,7 @@
 package org.argeo.cms.acr.xml;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -16,18 +18,22 @@ import org.argeo.api.acr.ContentNotFoundException;
 import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.CmsContentRepository;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
 public class DomContentProvider implements ContentProvider, NamespaceContext {
-       private Document document;
+       private final Document document;
 
        // XPath
        // TODO centralise in some executor?
        private final ThreadLocal<XPath> xPath;
 
-       public DomContentProvider(Document document) {
+       private String mountPath;
+
+       public DomContentProvider(String mountPath, Document document) {
+               this.mountPath = mountPath;
                this.document = document;
                this.document.normalizeDocument();
                XPathFactory xPathFactory = XPathFactory.newInstance();
@@ -77,6 +83,23 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
                }
        }
 
+       public void persist(ProvidedSession session) {
+               if (mountPath != null) {
+                       Content mountPoint = session.getMountPoint(mountPath);
+                       try (OutputStream out = mountPoint.open(OutputStream.class)) {
+                               CmsContentRepository contentRepository = (CmsContentRepository) session.getRepository();
+                               contentRepository.writeDom(document, out);
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot persist " + mountPath, e);
+                       }
+               }
+       }
+
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
        /*
         * 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
new file mode 100644 (file)
index 0000000..1029ba7
--- /dev/null
@@ -0,0 +1,41 @@
+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 {
+       public static void addNamespace(Element element, String prefix, String namespace) {
+               element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+                               namespace);
+       }
+
+//     public static void writeDom(TransformerFactory transformerFactory, Document document, OutputStream out)
+//                     throws IOException {
+//             try {
+//                     Transformer transformer = transformerFactory.newTransformer();
+//                     transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+//                     transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+//                     DOMSource source = new DOMSource(document);
+//                     StreamResult result = new StreamResult(out);
+//                     transformer.transform(source, result);
+//             } catch (TransformerException e) {
+//                     throw new IOException("Cannot write dom", e);
+//             }
+//     }
+
+       /** singleton */
+       private DomUtils() {
+
+       }
+}
index c2f0346ddc83e432731912bbbf044035cf236a23..c3cc26cf21448135f2d09685717e08c8c241c56c 100644 (file)
@@ -102,10 +102,9 @@ public class XmlNormalizer {
        }
 
        public static void main(String[] args) throws IOException {
+               Path dir = Paths.get(args[0]);
                XmlNormalizer xmlNormalizer = new XmlNormalizer();
-               DirectoryStream<Path> ds = Files.newDirectoryStream(
-                               Paths.get("/mnt/mbaudier/dev/git/unstable/argeo-suite/org.argeo.app.theme.default/icons/types/svg"),
-                               "*.svg");
+               DirectoryStream<Path> ds = Files.newDirectoryStream(dir, "*.svg");
                xmlNormalizer.normalizeXmlFiles(ds);
 
        }
index 1afd95cd403bed5dbf4b9b34135c82caf29e15a9..aaf02e06a5089911b6751d35aab00dd7c5865324 100644 (file)
@@ -4,14 +4,14 @@ import java.nio.file.Path;
 import java.util.Map;
 
 import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsState;
 import org.argeo.cms.acr.CmsContentRepository;
+import org.argeo.cms.acr.fs.FsContentProvider;
 import org.argeo.util.LangUtils;
 
 public class DeployedContentRepository extends CmsContentRepository {
        private final static String ROOT_XML = "cr:root.xml";
-       private final static String ACR_MOUNT_PATH = "acr.mount.path";
-
        private CmsState cmsState;
 
        @Override
@@ -20,9 +20,9 @@ public class DeployedContentRepository extends CmsContentRepository {
                Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML);
                initRootContentProvider(rootXml);
 
-//             Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
-//             FsContentProvider srvContentProvider = new FsContentProvider(srvPath, false);
-//             addProvider("/" + CmsConstants.SRV_WORKSPACE, srvContentProvider);
+               Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
+               FsContentProvider srvContentProvider = new FsContentProvider(CmsConstants.SRV_WORKSPACE, srvPath, false);
+               addProvider("/" + CmsConstants.SRV_WORKSPACE, srvContentProvider);
        }
 
        @Override
@@ -31,7 +31,7 @@ public class DeployedContentRepository extends CmsContentRepository {
        }
 
        public void addContentProvider(ContentProvider provider, Map<String, Object> properties) {
-               String base = LangUtils.get(properties, ACR_MOUNT_PATH);
+               String base = LangUtils.get(properties, CmsContentRepository.ACR_MOUNT_PATH_PROPERTY);
                addProvider(base, provider);
        }