X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;ds=sidebyside;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Facr%2Fxml%2FDomContent.java;h=0686be7cb59b2cf63d034fc8331a1d1884e39f08;hb=55d88fba80cec198a0f11ba7545e19878c51fc5e;hp=9e370db0c07ebfd552186300c59b677ff87952c3;hpb=85015a7cbfe5343c88477d828fa2f8fb754a65cd;p=lgpl%2Fargeo-commons.git
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..0686be7cb 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,30 +1,53 @@
package org.argeo.cms.acr.xml;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.CrName;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
+/** Content persisted as a DOM element. */
public class DomContent extends AbstractContent implements ProvidedContent {
- private final ProvidedSession session;
private final DomContentProvider provider;
private final Element element;
@@ -32,7 +55,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
private Boolean hasText = null;
public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.element = element;
}
@@ -43,9 +66,24 @@ public class DomContent extends AbstractContent implements ProvidedContent {
@Override
public QName getName() {
+ if (isLocalRoot()) {// root
+ String mountPath = provider.getMountPath();
+ if (mountPath != null) {
+ if (Content.ROOT_PATH.equals(mountPath)) {
+ return CrName.root.qName();
+ }
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ QName mountPointName = mountPoint.getName();
+ return mountPointName;
+ }
+ }
return toQName(this.element);
}
+ protected boolean isLocalRoot() {
+ return element.getParentNode() == null || element.getParentNode() instanceof Document;
+ }
+
protected QName toQName(Node node) {
String prefix = node.getPrefix();
if (prefix == null) {
@@ -93,24 +131,25 @@ public class DomContent extends AbstractContent implements ProvidedContent {
for (int i = 0; i < attributes.getLength(); i++) {
Attr attr = (Attr) attributes.item(i);
QName key = toQName(attr);
+ if (key.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
+ continue;// skip prefix mapping
result.add(key);
}
return result;
}
- @SuppressWarnings("unchecked")
+// @SuppressWarnings("unchecked")
@Override
public Optional get(QName key, Class clss) {
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
: key.getNamespaceURI();
if (element.hasAttributeNS(namespaceUriOrNull, key.getLocalPart())) {
String value = element.getAttributeNS(namespaceUriOrNull, key.getLocalPart());
- if (clss.isAssignableFrom(String.class))
- return Optional.of((A) value);
- else
- return Optional.empty();
+// if (isDefaultAttrTypeRequested(clss))
+// return Optional.of((A) CrAttributeType.parse(value));
+ return CrAttributeType.cast(clss, value);
} else
- return null;
+ return Optional.empty();
}
@Override
@@ -118,12 +157,31 @@ public class DomContent extends AbstractContent implements ProvidedContent {
Object previous = get(key);
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
: key.getNamespaceURI();
+ String prefixToUse = registerPrefixIfNeeded(key);
element.setAttributeNS(namespaceUriOrNull,
- namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+ namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(),
value.toString());
return previous;
}
+ protected String registerPrefixIfNeeded(QName name) {
+ String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+ : name.getNamespaceURI();
+ String prefixToUse;
+ if (namespaceUriOrNull != null) {
+ String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
+ if (registeredPrefix != null) {
+ prefixToUse = registeredPrefix;
+ } else {
+ provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
+ prefixToUse = name.getPrefix();
+ }
+ } else {
+ prefixToUse = null;
+ }
+ return prefixToUse;
+ }
+
@Override
public boolean hasText() {
// return element instanceof Text;
@@ -168,19 +226,27 @@ public class DomContent extends AbstractContent implements ProvidedContent {
@Override
public Iterator iterator() {
NodeList nodeList = element.getChildNodes();
- return new ElementIterator(this, session, provider, nodeList);
+ return new ElementIterator(this, getSession(), provider, nodeList);
}
@Override
public Content getParent() {
- Node parent = element.getParentNode();
- if (parent == null)
- return null;
- if (parent instanceof Document)
- return null;
- if (!(parent instanceof Element))
+ Node parentNode = element.getParentNode();
+ if (isLocalRoot()) {
+ String mountPath = provider.getMountPath();
+ if (mountPath == null)
+ return null;
+ if (Content.ROOT_PATH.equals(mountPath)) {
+ return null;
+ }
+ String[] parent = ContentUtils.getParentPath(mountPath);
+ if (ContentUtils.EMPTY.equals(parent[0]))
+ return null;
+ return getSession().get(parent[0]);
+ }
+ 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
@@ -189,8 +255,9 @@ public class DomContent extends AbstractContent implements ProvidedContent {
Document document = this.element.getOwnerDocument();
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
: name.getNamespaceURI();
+ String prefixToUse = registerPrefixIfNeeded(name);
Element child = document.createElementNS(namespaceUriOrNull,
- namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+ namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart());
element.appendChild(child);
return new DomContent(this, child);
}
@@ -211,10 +278,153 @@ public class DomContent extends AbstractContent implements ProvidedContent {
}
- public ProvidedSession getSession() {
- return session;
+ @SuppressWarnings("unchecked")
+ @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;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ DOMSource source = new DOMSource(element);
+ return (A) source;
+ }
+ return super.adapt(clss);
+ }
+
+ @SuppressWarnings("unchecked")
+ public CompletableFuture write(Class clss) {
+ if (String.class.isAssignableFrom(clss)) {
+ CompletableFuture res = new CompletableFuture<>();
+ res.thenAccept((s) -> {
+ getSession().notifyModification(this);
+ element.setTextContent(s);
+ });
+ return (CompletableFuture) res;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ CompletableFuture res = new CompletableFuture<>();
+ res.thenAccept((source) -> {
+ try {
+ Transformer transformer = provider.getTransformerFactory().newTransformer();
+ DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment();
+ DOMResult result = new DOMResult(documentFragment);
+ transformer.transform(source, result);
+ // Node parentNode = element.getParentNode();
+ Element resultElement = (Element) documentFragment.getFirstChild();
+ QName resultName = toQName(resultElement);
+ if (!resultName.equals(getName()))
+ throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName());
+
+ // attributes
+ NamedNodeMap attrs = resultElement.getAttributes();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true);
+ element.getAttributes().setNamedItem(attr2);
+ }
+
+ // Move all the children
+ while (element.hasChildNodes()) {
+ element.removeChild(element.getFirstChild());
+ }
+ while (resultElement.hasChildNodes()) {
+ element.appendChild(resultElement.getFirstChild());
+ }
+// parentNode.replaceChild(resultNode, element);
+// element = (Element)resultNode;
+
+ } catch (DOMException | TransformerException e) {
+ throw new RuntimeException("Cannot write to element", e);
+ }
+ });
+ return (CompletableFuture) res;
+ }
+ return super.write(clss);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public C open(Class clss) throws IOException, IllegalArgumentException {
+ if (InputStream.class.isAssignableFrom(clss)) {
+ PipedOutputStream out = new PipedOutputStream();
+ ForkJoinPool.commonPool().execute(() -> {
+ try {
+ Source source = new DOMSource(element);
+ Result result = new StreamResult(out);
+ provider.getTransformerFactory().newTransformer().transform(source, result);
+ out.flush();
+ out.close();
+ } catch (TransformerException | IOException e) {
+ throw new RuntimeException("Cannot read " + getPath(), e);
+ }
+ });
+ return (C) new PipedInputStream(out);
+ }
+ return super.open(clss);
+ }
+
+ @Override
+ public int getSiblingIndex() {
+ Node curr = element.getPreviousSibling();
+ int count = 1;
+ while (curr != null) {
+ if (curr instanceof Element) {
+ if (Objects.equals(curr.getNamespaceURI(), element.getNamespaceURI())
+ && Objects.equals(curr.getLocalName(), element.getLocalName())) {
+ count++;
+ }
+ }
+ curr = curr.getPreviousSibling();
+ }
+ return count;
+ }
+
+ /*
+ * TYPING
+ */
+ @Override
+ public List getContentClasses() {
+ List res = new ArrayList<>();
+ if (isLocalRoot()) {
+ String mountPath = provider.getMountPath();
+ if (Content.ROOT_PATH.equals(mountPath)) {// repository root
+ res.add(CrName.root.qName());
+ } else {
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ res.addAll(mountPoint.getContentClasses());
+ }
+ } else {
+ res.add(getName());
+ }
+ return res;
+ }
+
+ @Override
+ public void addContentClasses(QName... contentClass) {
+ if (isLocalRoot()) {
+ String mountPath = provider.getMountPath();
+ if (Content.ROOT_PATH.equals(mountPath)) {// repository root
+ throw new IllegalArgumentException("Cannot add content classes to repository root");
+ } else {
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ mountPoint.addContentClasses(contentClass);
+ }
+ } else {
+ super.addContentClasses(contentClass);
+ }
+ }
+
+ /*
+ * 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);
}
+ @Override
public DomContentProvider getProvider() {
return provider;
}