You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2022/04/05 15:15:09 UTC

[jackrabbit-filevault] branch master updated: JCRVLT-624 add write methods to DocViewNode2 (#218)

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git


The following commit(s) were added to refs/heads/master by this push:
     new 415a858c JCRVLT-624 add write methods to DocViewNode2 (#218)
415a858c is described below

commit 415a858cc5816f58b8010172360a69703e58b953
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Tue Apr 5 17:15:04 2022 +0200

    JCRVLT-624 add write methods to DocViewNode2 (#218)
    
    This allows to write enhanced docview XML without an underlying
    repository.
---
 .../vault/fs/impl/io/DocViewSAXFormatter.java      | 104 ++++----------------
 .../apache/jackrabbit/vault/util/DocViewNode2.java | 108 ++++++++++++++++++++-
 .../vault/util/DocViewProperty2Comparator.java     |  51 ++++++++++
 .../apache/jackrabbit/vault/util/package-info.java |   2 +-
 4 files changed, 174 insertions(+), 91 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java
index 5b441b46..166f8766 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXFormatter.java
@@ -18,8 +18,10 @@
 package org.apache.jackrabbit.vault.fs.impl.io;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -33,20 +35,13 @@ import javax.xml.stream.XMLStreamWriter;
 
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
-import org.apache.jackrabbit.spi.commons.conversion.NameException;
-import org.apache.jackrabbit.spi.commons.conversion.NameParser;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
-import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.spi.commons.namespace.SessionNamespaceResolver;
-import org.apache.jackrabbit.util.ISO9075;
-import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.vault.fs.api.Aggregate;
 import org.apache.jackrabbit.vault.fs.api.VaultFsConfig;
-import org.apache.jackrabbit.vault.util.DocViewProperty2;
-import org.apache.jackrabbit.vault.util.ItemNameComparator2;
+import org.apache.jackrabbit.vault.util.DocViewNode2;
 import org.apache.jackrabbit.vault.util.JcrConstants;
-import org.xml.sax.SAXException;
 
 /**
  * Writes the enhanced docview XML based on the aggregate tree to a given {@link XMLStreamWriter}.
@@ -125,15 +120,12 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
         IGNORED_PROTECTED_PROPERTIES = Collections.unmodifiableSet(props);
     }
 
-    private ItemNameComparator2 itemNameComparator;
-
     protected DocViewSAXFormatter(Aggregate aggregate, XMLStreamWriter writer)
             throws RepositoryException {
 
         this.aggregate = aggregate;
         this.session = aggregate.getNode().getSession();
         nsResolver = new SessionNamespaceResolver(session);
-        itemNameComparator = new ItemNameComparator2(nsResolver);
         this.writer = writer;
 
         DefaultNamePathResolver npResolver = new DefaultNamePathResolver(nsResolver);
@@ -155,39 +147,6 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
         useBinaryReferences = "true".equals(aggregate.getManager().getConfig().getProperty(VaultFsConfig.NAME_USE_BINARY_REFERENCES));
     }
 
-    /**
-     * Starts namespace declarations
-     *
-     * @throws RepositoryException if a repository error occurs
-     * @throws SAXException if the underlying content handler throws a sax exception
-     */
-    private void startNamespaceDeclarations() throws RepositoryException, XMLStreamException {
-        // always include jcr namespace (see JCRVLT-266)
-        
-        writer.writeNamespace(Name.NS_JCR_PREFIX, Name.NS_JCR_URI);
-
-        for (String prefix: aggregate.getNamespacePrefixes()) {
-            if (Name.NS_XML_PREFIX.equals(prefix)) {
-                // skip 'xml' prefix as this would be an illegal namespace declaration
-                continue;
-            }
-            if (Name.NS_JCR_PREFIX.equals(prefix)) {
-                continue;
-            }
-            writer.writeNamespace(prefix, aggregate.getNamespaceURI(prefix));
-        }
-    }
-
-    private Name getQName(String rawName) throws RepositoryException {
-        try {
-            return NameParser.parse(rawName, nsResolver, NameFactoryImpl.getInstance());
-        } catch (NameException e) {
-            // should never get here...
-            String msg = "internal error: failed to resolve namespace mappings";
-            throw new RepositoryException(msg, e);
-        }
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -228,45 +187,18 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
      */
     @Override
     public void onChildren(Node node, int level) throws RepositoryException {
-        String label = Text.getName(node.getPath());
-        final String elemName;
-        if (level == 0) {
-            // root node needs a special name
-            elemName = jcrRoot;
-        } else {
-            // encode node name to make sure it's a valid xml name
-            elemName = ISO9075.encode(label);
-        }
-
-        Collections.sort(props, itemNameComparator);
-
-        // start element (node)
-        Name qName = getQName(elemName);
         try {
-            // with namespace?
-            String namespaceUri = qName.getNamespaceURI();
-            if (namespaceUri.length()>0) {
-                writer.writeStartElement(nsResolver.getPrefix(namespaceUri), qName.getLocalName(), namespaceUri);
-            } else {
-                writer.writeStartElement(qName.getLocalName());
-            }
+            DocViewNode2 docViewNode = DocViewNode2.fromNode(node, level == 0, props, useBinaryReferences);
+            final Set<String> namespacePrefixes;
             if (level == 0) {
-                startNamespaceDeclarations();
-            }
-            for (Property prop: props) {
-                // attribute name (encode property name to make sure it's a valid xml name)
-                String attrName = ISO9075.encode(prop.getName());
-                Name qAttributeName = getQName(attrName);
-                boolean sort = qAttributeName.equals(NameConstants.JCR_MIXINTYPES);
-                String attributeNamespaceUri = qAttributeName.getNamespaceURI();
-                if (attributeNamespaceUri.length()>0) {
-                    writer.writeAttribute(nsResolver.getPrefix(attributeNamespaceUri), attributeNamespaceUri, qAttributeName.getLocalName(), 
-                            DocViewProperty2.format(prop, sort, useBinaryReferences));
-                } else {
-                    writer.writeAttribute(qAttributeName.getLocalName(), DocViewProperty2.format(prop, sort, useBinaryReferences));
-                }
-               
+                namespacePrefixes = new LinkedHashSet<>();
+                // always include jcr namespace (see JCRVLT-266)
+                namespacePrefixes.add(Name.NS_JCR_PREFIX);
+                namespacePrefixes.addAll(Arrays.asList(aggregate.getNamespacePrefixes()));
+            } else {
+                namespacePrefixes = Collections.emptySet();
             }
+            docViewNode.writeStart(writer, nsResolver, namespacePrefixes);
         } catch (XMLStreamException e) {
             throw new RepositoryException(e);
         }
@@ -279,7 +211,7 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
     public void onNodeEnd(Node node, boolean included, int level) throws RepositoryException {
         // end element (node)
         try {
-            writer.writeEndElement();
+            DocViewNode2.writeEnd(writer);
         } catch (XMLStreamException e) {
             throw new RepositoryException(e);
         }
@@ -302,13 +234,11 @@ public class DocViewSAXFormatter implements AggregateWalkListener {
      */
     @Override
     public void onNodeIgnored(Node node, int depth) throws RepositoryException {
-        // just add an empty node. used for ordering
-        String label = Text.getName(node.getPath());
-        String elemName = ISO9075.encode(label);
-        Name qName = getQName(elemName);
         try {
-            writer.writeStartElement(nsResolver.getPrefix(qName.getNamespaceURI()), qName.getLocalName(), qName.getNamespaceURI());
-            writer.writeEndElement();
+            // just add an empty node. used for ordering
+            DocViewNode2 docViewNode = DocViewNode2.fromNode(node, false, Collections.emptyList(), useBinaryReferences);
+            docViewNode.writeStart(writer, nsResolver, Collections.emptyList());
+            DocViewNode2.writeEnd(writer);
         } catch (XMLStreamException e) {
             throw new RepositoryException(e);
         }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode2.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode2.java
index ec35aeaf..e065dabe 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode2.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode2.java
@@ -16,20 +16,35 @@
  ************************************************************************/
 package org.apache.jackrabbit.vault.util;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import javax.jcr.NamespaceException;
 import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
 
+import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.conversion.ParsingNameResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+import org.apache.jackrabbit.spi.commons.namespace.SessionNamespaceResolver;
+import org.apache.jackrabbit.util.ISO9075;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -68,6 +83,32 @@ public class DocViewNode2 {
         return new DocViewNode2(name, index, properties);
     }
 
+    @SuppressWarnings("unchecked")
+    public static @NotNull DocViewNode2 fromNode(@NotNull Node node, boolean isRoot, boolean useBinaryReferences) throws RepositoryException {
+        return fromNode(node, isRoot, JcrUtils.in((Iterator<Property>)node.getProperties()), useBinaryReferences);
+    }
+
+    public static @NotNull DocViewNode2 fromNode(@NotNull Node node, boolean isRoot, @NotNull Iterable<Property> properties, boolean useBinaryReferences) throws RepositoryException {
+        NameResolver nameResolver = new ParsingNameResolver(NameFactoryImpl.getInstance(), new SessionNamespaceResolver(node.getSession()));
+        final Name nodeName;
+        if (isRoot) {
+            nodeName = NameConstants.JCR_ROOT;
+        } else {
+            nodeName = nameResolver.getQName(node.getName());
+        }
+        Collection<DocViewProperty2> docViewProps = new ArrayList<>();
+        for (Property property : properties) {
+            Name propertyName =  nameResolver.getQName(property.getName());
+            boolean sort = propertyName.equals(NameConstants.JCR_MIXINTYPES);
+            docViewProps.add(DocViewProperty2.fromProperty(property, sort, useBinaryReferences));
+        }
+        int index = node.getIndex();
+        if (index == 1) {
+            index = 0; // we use 0 here, if no index necessary
+        }
+        return new DocViewNode2(nodeName, index, docViewProps);
+    }
+
     /** 
      * 
      * @return the name of the {@link Node} represented by this class
@@ -78,8 +119,8 @@ public class DocViewNode2 {
 
     /**
      * 
-     * @return 0, except if there is a same-name sibling in the docview. In that case the index gives the order of the SNS nodes.
-     *  @see <a href="https://s.apache.org/jcr-2.0-spec/22_Same-Name_Siblings.html#22.2%20Addressing%20Same-Name%20Siblings%20by%20Path">Same-Name Siblings</a>
+     * @return 0, except if there is a same-name sibling in the docview. In that case the index gives the 1-based order of the SNS nodes.
+     * @see <a href="https://s.apache.org/jcr-2.0-spec/22_Same-Name_Siblings.html#22.2%20Addressing%20Same-Name%20Siblings%20by%20Path">Same-Name Siblings</a>
      */
     public int getIndex() {
         return index;
@@ -162,7 +203,68 @@ public class DocViewNode2 {
         return index == other.index && areNamesEqual(name, other.name) && Objects.equals(properties, other.properties);
     }
 
-    static boolean areNamesEqual(@NotNull Name name,  @NotNull Name otherName ) {
+    static boolean areNamesEqual(@NotNull Name name, @NotNull Name otherName) {
         return Objects.equals(name.getLocalName(), otherName.getLocalName()) && Objects.equals(name.getNamespaceURI(), otherName.getNamespaceURI());
     }
+
+    /**
+     * Writes the node's start tag (including the attributes for the properties and optionally the namespace declarations) to the given {@link XMLStreamWriter}.
+     * Use the following writer for properly formatting the output according to FileVault standards:
+     * {@code FormattingXmlStreamWriter.create(out, new DocViewFormat().getXmlOutputFormat())}.
+     * 
+     * @param writer the XMLStreamWriter to write to
+     * @param nsResolver the namespace resolver to use for retrieving prefixes for namespace URIs of {@link #getName()} and {@link DocViewProperty2#getName()}
+     * @param namespacePrefixes the namespace prefixes for which to emit namespace declarations in this node
+     * @throws NamespaceException in case no prefix is defined for the namespace URI of a name (either node's or property's)
+     * @throws XMLStreamException
+     * @since 3.6.2
+     */
+    public void writeStart(@NotNull XMLStreamWriter writer, @NotNull NamespaceResolver nsResolver, @NotNull Iterable<String> namespacePrefixes) throws NamespaceException, XMLStreamException {
+        // sort properties
+        Set<DocViewProperty2> props = new TreeSet<>(new DocViewProperty2Comparator(nsResolver));
+        props.addAll(properties.values());
+        // start element (node)
+        // with namespace?
+        String namespaceUri = getName().getNamespaceURI();
+        String localName = getName().getLocalName();
+        if (getIndex() > 1) {
+            localName += "[" + getIndex() + "]";
+        }
+        String encodedLocalName = ISO9075.encode(localName);
+       
+        if (namespaceUri.length()>0) {
+            writer.writeStartElement(nsResolver.getPrefix(namespaceUri), encodedLocalName, namespaceUri);
+        } else {
+            writer.writeStartElement(encodedLocalName);
+        }
+        for (String namespacePrefix : namespacePrefixes) {
+            if (Name.NS_XML_PREFIX.equals(namespacePrefix)) {
+                // skip 'xml' prefix as this would be an illegal namespace declaration
+                continue;
+            }
+            writer.writeNamespace(namespacePrefix, nsResolver.getURI(namespacePrefix));
+        }
+        for (DocViewProperty2 prop: props) {
+            String attributeLocalName = ISO9075.encode(prop.getName().getLocalName());
+            String attributeNamespaceUri = prop.getName().getNamespaceURI();
+            if (attributeNamespaceUri.length()>0) {
+                writer.writeAttribute(nsResolver.getPrefix(attributeNamespaceUri), attributeNamespaceUri, attributeLocalName, 
+                        prop.formatValue());
+            } else {
+                writer.writeAttribute(attributeLocalName, prop.formatValue());
+            }
+        }
+    }
+
+    /**
+     * Writes the node's end tag to the given {@link XMLStreamWriter}.
+     * Use the following writer for properly formatting the output according to FileVault standards:
+     * {@code FormattingXmlStreamWriter.create(out, new DocViewFormat().getXmlOutputFormat())}.
+     * @param writer the XMLStreamWriter to write to
+     * @throws XMLStreamException
+     * @since 3.6.2
+     */
+    public static void writeEnd(@NotNull XMLStreamWriter writer) throws XMLStreamException {
+        writer.writeEndElement();
+    }
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2Comparator.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2Comparator.java
new file mode 100644
index 00000000..29bba1ca
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2Comparator.java
@@ -0,0 +1,51 @@
+/*************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ************************************************************************/
+package org.apache.jackrabbit.vault.util;
+
+import java.util.Comparator;
+
+import javax.jcr.RepositoryException;
+import javax.xml.namespace.QName;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+
+/** 
+ * Similar to {@link QNameComparator} but acting on {@link DocViewProperty2} leveraging its name and a given namespace resolver
+ */
+public final class DocViewProperty2Comparator implements Comparator<DocViewProperty2> {
+
+    private final NamespaceResolver nsResolver;
+
+    public DocViewProperty2Comparator(NamespaceResolver nsResolver) {
+        this.nsResolver = nsResolver;
+    }
+
+    private QName getQName(Name name) throws RepositoryException {
+        return new QName(name.getNamespaceURI(), name.getLocalName(), nsResolver.getPrefix(name.getNamespaceURI()));
+    }
+
+    @Override
+    public int compare(DocViewProperty2 o1, DocViewProperty2 o2) {
+        try {
+            return QNameComparator.INSTANCE.compare(getQName(o1.getName()), getQName(o2.getName()));
+        } catch (RepositoryException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/package-info.java
index 82c0e8b0..27fff62d 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.9.0")
+@Version("2.10.0")
 package org.apache.jackrabbit.vault.util;
 
 import org.osgi.annotation.versioning.Version;
\ No newline at end of file