From: Mathieu Baudier Date: Wed, 1 Jun 2022 08:34:07 +0000 (+0200) Subject: Start supporting mounting of XML with FS. X-Git-Tag: v2.3.10~209 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=e3db2eba9a7f8380a6f76d7b0e6cd4825e91893e;p=lgpl%2Fargeo-commons.git Start supporting mounting of XML with FS. --- 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 c6d738020..ddf409e0b 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(basePath, true); + FsContentProvider contentSession = new FsContentProvider(null, basePath, true); // GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/")); shell.setSize(shell.computeSize(800, 600)); diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java index 9f78577b5..134f68162 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -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); } diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java index a7005ce23..f7bfd0949 100644 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java @@ -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 sessionAdapters = Collections.synchronizedMap(new HashMap<>()); - public void start() { + public void start(Map 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 */ diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java index aeea27ef2..c2202b0fa 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java @@ -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, Map { Optional> getMultiple(QName key, Class clss); + @SuppressWarnings("unchecked") default List getMultiple(QName key) { Class type; try { @@ -70,6 +72,13 @@ public interface Content extends Iterable, Map { /* * CONTENT OPERATIONS */ +// default CompletionStage edit(Consumer 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, Map { * DEFAULT METHODS */ default A adapt(Class 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 open(Class clss) throws IOException { + throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName()); } - default C open(Class clss) throws IOException, IllegalArgumentException { - throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName()); + default CompletableFuture write(Class clss) { + throw new UnsupportedOperationException("Cannot write content " + this + " as " + clss.getName()); } /* diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java index e3a09f603..3adde724b 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java @@ -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 edit(Consumer work); } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java index 9d2215f65..c07a43b6e 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java @@ -10,6 +10,8 @@ public interface ContentProvider extends NamespaceContext { Content get(ProvidedSession session, String mountPath, String relativePath); + String getMountPath(); + /* * NAMESPACE CONTEXT */ diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java index d9fc781d0..d483e7fcc 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java @@ -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"); + } } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java index cde027baf..dcf49d0d7 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java @@ -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); } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java index 60f64def8..7c6912c4e 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java @@ -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 onClose(); + Content getMountPoint(String path); + + boolean isEditing(); /* * NAMESPACE CONTEXT */ diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java index 50438e89a..d91fb16fe 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -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 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 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 registeredTypes = contentTypesManager.listTypes(); + if (registeredTypes.contains(firstType)) + return true; + return false; + } + /* * NAMESPACE CONTEXT */ @@ -229,6 +279,8 @@ public class CmsContentRepository implements ProvidedRepository { private CompletableFuture closed = new CompletableFuture<>(); + private CompletableFuture 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 edit(Consumer 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); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java index 23f2d9001..48093bee0 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentTypesManager.java @@ -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 prefixes = new TreeMap<>(); + // immutable factories + private SchemaFactory schemaFactory; + + /** Schema sources. */ private List sources = new ArrayList<>(); - private SchemaFactory schemaFactory; + // cached private Schema schema; + DocumentBuilderFactory documentBuilderFactory; + private XSModel xsModel; + private NavigableSet 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 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 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 getPrefixes() { return prefixes; } 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 b8bbddb50..a2cc52c42 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 @@ -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 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 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 open(Class 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 */ 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 37401a29c..3445733e1 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 @@ -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 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 { 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 9e370db0c..9a1a58c55 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 @@ -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 adapt(Class 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 CompletableFuture write(Class clss) { + if (String.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture<>(); + res.thenAccept((s) -> element.setTextContent(s));// .thenRun(() -> provider.persist(session)); + return (CompletableFuture) 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; } 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 4db493433..8caac1ad2 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 @@ -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; - 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 index 000000000..1029ba79f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java @@ -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() { + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java index c2f0346dd..c3cc26cf2 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java @@ -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 ds = Files.newDirectoryStream( - Paths.get("/mnt/mbaudier/dev/git/unstable/argeo-suite/org.argeo.app.theme.default/icons/types/svg"), - "*.svg"); + DirectoryStream ds = Files.newDirectoryStream(dir, "*.svg"); xmlNormalizer.normalizeXmlFiles(ds); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java index 1afd95cd4..aaf02e06a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java @@ -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 properties) { - String base = LangUtils.get(properties, ACR_MOUNT_PATH); + String base = LangUtils.get(properties, CmsContentRepository.ACR_MOUNT_PATH_PROPERTY); addProvider(base, provider); }