You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by tr...@apache.org on 2018/03/27 04:38:15 UTC

svn commit: r1827801 - in /jackrabbit/commons/filevault/trunk: vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ vault-c...

Author: tripod
Date: Tue Mar 27 04:38:14 2018
New Revision: 1827801

URL: http://svn.apache.org/viewvc?rev=1827801&view=rev
Log:
JCRVLT-271 Support a CLI command to format vault xml files

(closes #24)

Added:
    jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/CmdFormatCli.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/DocViewFormat.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/AttributeNameComparator.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/DocViewFormatTest.java
    jackrabbit/commons/filevault/trunk/vault-core/src/test/resources/org/apache/jackrabbit/vault/fs/io/DocViewFormat/
    jackrabbit/commons/filevault/trunk/vault-core/src/test/resources/org/apache/jackrabbit/vault/fs/io/DocViewFormat/malformed.xml
Modified:
    jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VaultFsApp.java
    jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VltExecutionContext.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSerializer.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ItemNameComparator.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/OutputFormat.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/XMLSerializer.java
    jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/package-info.java
    jackrabbit/commons/filevault/trunk/vault-doc/src/site/markdown/usage.md

Added: jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/CmdFormatCli.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/CmdFormatCli.java?rev=1827801&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/CmdFormatCli.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/CmdFormatCli.java Tue Mar 27 04:38:14 2018
@@ -0,0 +1,150 @@
+/*
+ * 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.cli;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.commons.cli2.CommandLine;
+import org.apache.commons.cli2.Option;
+import org.apache.commons.cli2.builder.ArgumentBuilder;
+import org.apache.commons.cli2.builder.CommandBuilder;
+import org.apache.commons.cli2.builder.DefaultOptionBuilder;
+import org.apache.commons.cli2.builder.GroupBuilder;
+import org.apache.commons.cli2.option.Command;
+import org.apache.jackrabbit.vault.fs.io.DocViewFormat;
+import org.apache.jackrabbit.vault.util.console.CliCommand;
+
+public class CmdFormatCli extends AbstractVaultCommand {
+
+    private static final Pattern DEFAULT_PATTERN = Pattern.compile(".*\\.xml");
+
+    private Option optCheckOnly;
+    private Option optPatterns;
+    private Option argPaths;
+
+
+    @Override
+    protected void doExecute(VaultFsApp app, CommandLine cl) throws Exception {
+        boolean checkOnly = cl.hasOption(optCheckOnly);
+        boolean verbose = cl.hasOption(OPT_VERBOSE);
+        List<String> givenPatterns = (List<String>) cl.getValues(optPatterns);
+        List<Pattern> parsedPatterns = new ArrayList<>(givenPatterns.size());
+
+        for (String pattern : givenPatterns) {
+            parsedPatterns.add(Pattern.compile(pattern));
+        }
+        if (parsedPatterns.isEmpty()) {
+            parsedPatterns.add(DEFAULT_PATTERN);
+        }
+
+        List<String> localPaths = cl.getValues(argPaths);
+        List<File> localFiles = app.getPlatformFiles(localPaths, true);
+        if (localFiles.isEmpty()) {
+            localFiles.add(app.getPlatformFile(".", true));
+        }
+
+        List<String> formattedFiles = new LinkedList<>();
+        DocViewFormat format = new DocViewFormat();
+        for (File file: localFiles) {
+            if (file.isDirectory()) {
+                if (verbose) {
+                    System.out.printf("traversing: %s%n", file);
+                    for (Pattern p: parsedPatterns) {
+                        System.out.printf("scanning for files matching: %s%n", p);
+                    }
+                }
+                formattedFiles.addAll(format.format(file, parsedPatterns, checkOnly));
+            } else {
+                if (verbose) {
+                    System.out.printf("processing: %s%n", file);
+                }
+                if (format.format(file, checkOnly)) {
+                    formattedFiles.add(file.getPath());
+                }
+            }
+        }
+        if (formattedFiles.isEmpty()) {
+            System.out.println("All files already properly formatted.");
+            return;
+        }
+
+        final Path cwd = Paths.get(new File("").getAbsolutePath());
+        if (checkOnly) {
+            System.out.println("The following files are not properly formatted:\n");
+            for (String path: formattedFiles) {
+                System.out.println(cwd.relativize(Paths.get(path)));
+            }
+        } else {
+            System.out.println("reformatted files:\n");
+            for (String path: formattedFiles) {
+                System.out.println(cwd.relativize(Paths.get(path)));
+            }
+        }
+    }
+
+    @Override
+    protected Command createCommand() {
+        return new CommandBuilder()
+                .withName("format")
+                .withDescription(getShortDescription())
+                .withChildren(new GroupBuilder()
+                        .withOption(CliCommand.OPT_VERBOSE)
+                        .withOption(optCheckOnly = new DefaultOptionBuilder()
+                            .withShortName("c")
+                            .withLongName("check-only")
+                            .withDescription("Only check the format.")
+                            .create()
+                        )
+                        .withOption(optPatterns = new DefaultOptionBuilder()
+                                .withShortName("p")
+                                .withLongName("pattern")
+                                .withDescription("pattern for recursive format. defaults to match all xml files.")
+                                .withArgument(new ArgumentBuilder()
+                                        .withMinimum(0)
+                                        .create())
+                                .create())
+                        .withOption(argPaths = new ArgumentBuilder()
+                                .withName("paths")
+                                .withDescription("files or directories to format.")
+                                .withMinimum(0)
+                                .create()
+                        )
+                        .create())
+                .create();
+    }
+
+    @Override
+    public String getShortDescription() {
+        return "Formats vault docview files.";
+    }
+
+    @Override
+    public String getLongDescription() {
+        return  "Formats the file specified by <path> according to the vault specific docview format." +
+                "If the <path> points at a directory, the files matching the patterns are processed recursively.\n\n" +
+                "Example:\n" +
+                "  vlt format -c -p '\\\\.content\\\\.xml' content/jcr_root\n\n";
+    }
+
+}

Modified: jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VaultFsApp.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VaultFsApp.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VaultFsApp.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VaultFsApp.java Tue Mar 27 04:38:14 2018
@@ -225,7 +225,7 @@ public class VaultFsApp extends Abstract
 
     @Override
     public String getCopyrightLine() {
-        return "Copyright 2013 by Apache Software Foundation. See LICENSE.txt for more information.";
+        return "Copyright 2018 by Apache Software Foundation. See LICENSE.txt for more information.";
     }
 
     protected void mount(String creds, String wsp, String root, String config,

Modified: jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VltExecutionContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VltExecutionContext.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VltExecutionContext.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-cli/src/main/java/org/apache/jackrabbit/vault/cli/VltExecutionContext.java Tue Mar 27 04:38:14 2018
@@ -51,6 +51,7 @@ public class VltExecutionContext extends
         installCommand(new CmdDiff());
         installCommand(new CmdRcp());
         installCommand(new CmdSync());
+        installCommand(new CmdFormatCli());
 
         //installCommand(new CmdVaultDebug());
     }

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSerializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSerializer.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSerializer.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSerializer.java Tue Mar 27 04:38:14 2018
@@ -25,8 +25,8 @@ import javax.jcr.RepositoryException;
 import org.apache.jackrabbit.vault.fs.api.Aggregate;
 import org.apache.jackrabbit.vault.fs.api.SerializationType;
 import org.apache.jackrabbit.vault.fs.impl.AggregateImpl;
+import org.apache.jackrabbit.vault.fs.io.DocViewFormat;
 import org.apache.jackrabbit.vault.fs.io.Serializer;
-import org.apache.jackrabbit.vault.util.xml.serialize.OutputFormat;
 import org.apache.jackrabbit.vault.util.xml.serialize.XMLSerializer;
 
 /**
@@ -53,11 +53,7 @@ public class DocViewSerializer implement
      */
     public void writeContent(OutputStream out) throws IOException, RepositoryException {
         // build content handler and add filter in case of original xml files
-        OutputFormat oFmt = new OutputFormat("xml", "UTF-8", true);
-        oFmt.setIndent(4);
-        oFmt.setLineWidth(0);
-        oFmt.setBreakEachAttribute(true);
-        XMLSerializer ser = new XMLSerializer(out, oFmt);
+        XMLSerializer ser = new XMLSerializer(out, new DocViewFormat().getXmlOutputFormat());
         DocViewSAXFormatter fmt = new DocViewSAXFormatter(aggregate, ser);
         aggregate.walk(fmt);
     }

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/DocViewFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/DocViewFormat.java?rev=1827801&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/DocViewFormat.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/DocViewFormat.java Tue Mar 27 04:38:14 2018
@@ -0,0 +1,183 @@
+/*
+ * 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.fs.io;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.zip.CRC32;
+import java.util.zip.CheckedInputStream;
+import java.util.zip.CheckedOutputStream;
+import java.util.zip.Checksum;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.vault.util.xml.serialize.AttributeNameComparator;
+import org.apache.jackrabbit.vault.util.xml.serialize.OutputFormat;
+import org.apache.jackrabbit.vault.util.xml.serialize.XMLSerializer;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * This class provides access to the commonly used doc view xml format and functionality that checks files for the format or reformats
+ * them accordingly.
+ */
+public class DocViewFormat {
+
+    private final OutputFormat format;
+    private WeakReference<ByteArrayOutputStream> formattingBuffer;
+
+    public DocViewFormat() {
+        format = new OutputFormat("xml", "UTF-8", true);
+        format.setIndent(4);
+        format.setLineWidth(0);
+        format.setBreakEachAttribute(true);
+        format.setSortAttributeNamesBy(AttributeNameComparator.INSTANCE);
+    }
+
+    /**
+     * Returns the {@link OutputFormat} used by {@link org.apache.jackrabbit.vault.fs.impl.io.DocViewSerializer} when writing doc view xml
+     * files.
+     *
+     * @return the output format
+     */
+    public OutputFormat getXmlOutputFormat() {
+        return format;
+    }
+
+    /**
+     * Formats a given file using the {@link OutputFormat} returned by {@link DocViewFormat#getXmlOutputFormat()}.
+     * The file is replaced on disk but only if wasn't already formatted correctly and if {@code dryRun} is {@code false}.
+     *
+     * @param file the file to format
+     * @return {@code true} if the formatted version differs from the original.
+     * @throws IOException if an I/O error occurs
+     */
+    public boolean format(File file, boolean dryRun) throws IOException {
+        CRC32 originalCrc32 = new CRC32();
+        CRC32 formattedCrc32 = new CRC32();
+        byte[] formatted = format(file, originalCrc32, formattedCrc32);
+
+        final boolean changed = originalCrc32.getValue() != formattedCrc32.getValue();
+        if (changed && !dryRun) {
+            try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+                IOUtils.copy(new ByteArrayInputStream(formatted), out);
+            }
+        }
+        return changed;
+    }
+
+    /**
+     * Formats given files using the {@link OutputFormat} returned by {@link DocViewFormat#getXmlOutputFormat()} by traversing the directory
+     * tree given as file. Only those files will be formatted, that have a filename matching at least one of the given filenamePatterns,
+     * and only if {@code dryRun} is {@code false}.
+     *
+     * @param directory the start directory
+     * @param filenamePatterns list of regexp patterns
+     * @return a list of relative paths of those files which are not formatted correctly according to {@link #format(File, boolean)}
+     * @throws IOException in case there is an exception during traversal or formatting. That means formatting will fail on the first error that appeared
+     */
+    public List<String> format(File directory, List<Pattern> filenamePatterns, final boolean dryRun) throws IOException {
+        final List<String> changed = new LinkedList<>();
+        Files.walkFileTree(directory.toPath(), new AbstractFormattingVisitor(filenamePatterns) {
+            @Override protected void process(File file) throws IOException {
+                if (format(file, dryRun)) {
+                    changed.add(file.getPath());
+                };
+            }
+        });
+        return changed;
+    }
+
+    /**
+     * internally formats the given file and computes their checksum
+     * @param file the file
+     * @param original checksum of the original file
+     * @param formatted checksum of the formatted file
+     * @return the formatted bytes
+     * @throws IOException if an error occurs
+     */
+    private byte[] format(File file, Checksum original, Checksum formatted) throws IOException {
+        try (InputStream in = new CheckedInputStream(new BufferedInputStream(new FileInputStream(file)), original)) {
+            @SuppressWarnings("resource")
+            ByteArrayOutputStream buffer = formattingBuffer != null ? formattingBuffer.get() : null;
+            if (buffer == null) {
+                buffer = new ByteArrayOutputStream();
+                formattingBuffer = new WeakReference<>(buffer);
+            } else {
+                buffer.reset();
+            }
+
+            XMLSerializer serializer = new XMLSerializer(new CheckedOutputStream(buffer, formatted), format);
+            XMLReader reader = XMLReaderFactory.createXMLReader();
+            reader.setContentHandler(serializer);
+            reader.setDTDHandler(serializer);
+            reader.parse(new InputSource(in));
+
+            return buffer.toByteArray();
+        } catch (SAXException ex) {
+            throw new IOException(ex);
+        }
+    }
+
+    private abstract static class AbstractFormattingVisitor extends SimpleFileVisitor<Path> {
+
+        private final List<Pattern> patterns;
+
+        AbstractFormattingVisitor(List<Pattern> patterns) {
+            this.patterns = patterns;
+        }
+
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+            if (Files.isRegularFile(file) && isIncluded(file)) {
+                process(file.toFile());
+            }
+            return super.visitFile(file, attrs);
+        }
+
+        private boolean isIncluded(Path file) {
+            for (Pattern pattern : patterns) {
+                if (pattern.matcher(file.getFileName().toString()).matches()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        protected abstract void process(File file) throws IOException;
+    }
+}

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/package-info.java Tue Mar 27 04:38:14 2018
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.7.0")
+@Version("2.8.0")
 package org.apache.jackrabbit.vault.fs.io;
 
 import org.osgi.annotation.versioning.Version;
\ No newline at end of file

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ItemNameComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ItemNameComparator.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ItemNameComparator.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/ItemNameComparator.java Tue Mar 27 04:38:14 2018
@@ -22,6 +22,8 @@ import java.util.Comparator;
 import javax.jcr.Item;
 import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.vault.util.xml.serialize.AttributeNameComparator;
+
 /**
  * {@code ItemNameComparator}...
  */
@@ -32,17 +34,9 @@ public class ItemNameComparator implemen
     public int compare(Item o1, Item o2) {
         try {
             // sort namespaced first
-            String n1 = o1.getName().toLowerCase();
-            String n2 = o2.getName().toLowerCase();
-            int i1 = n1.indexOf(':');
-            int i2 = n2.indexOf(':');
-            if (i1 >=0 && i2 < 0) {
-                return -1;
-            } else if (i1 < 0 && i2 >=0) {
-                return 1;
-            } else {
-                return n1.compareTo(n2);
-            }
+            String n1 = o1.getName();
+            String n2 = o2.getName();
+            return AttributeNameComparator.INSTANCE.compare(n1, n2);
         } catch (RepositoryException e) {
             throw new IllegalStateException(e);
         }

Added: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/AttributeNameComparator.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/AttributeNameComparator.java?rev=1827801&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/AttributeNameComparator.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/AttributeNameComparator.java Tue Mar 27 04:38:14 2018
@@ -0,0 +1,50 @@
+/*
+ * 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.xml.serialize;
+
+import java.util.Comparator;
+
+import org.apache.jackrabbit.vault.util.xml.xerces.util.XMLSymbols;
+
+public class AttributeNameComparator implements Comparator<String> {
+
+    public static final AttributeNameComparator INSTANCE = new AttributeNameComparator();
+
+    @Override
+    public int compare(String o1, String o2) {
+        String n1 = o1.toLowerCase();
+        String n2 = o2.toLowerCase();
+        // order xmlns(:<prefix>)? attributes always to the front
+        boolean isXmlNs1 = n1.startsWith(XMLSymbols.PREFIX_XMLNS);
+        boolean isXmlNs2 = n2.startsWith(XMLSymbols.PREFIX_XMLNS);
+        if (isXmlNs1 && !isXmlNs2) {
+            return -1;
+        } else if (!isXmlNs1 && isXmlNs2) {
+            return 1;
+        }
+        int i1 = n1.indexOf(':');
+        int i2 = n2.indexOf(':');
+        if (i1 >=0 && i2 < 0) {
+            return -1;
+        } else if (i1 < 0 && i2 >=0) {
+            return 1;
+        } else {
+            return n1.compareTo(n2);
+        }
+    }
+}

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/OutputFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/OutputFormat.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/OutputFormat.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/OutputFormat.java Tue Mar 27 04:38:14 2018
@@ -28,6 +28,7 @@ package org.apache.jackrabbit.vault.util
 
 
 import java.io.UnsupportedEncodingException;
+import java.util.Comparator;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.DocumentType;
@@ -235,6 +236,10 @@ public class OutputFormat {
      * serialized. Defaults to false for backwards compatibility.
      */
     private boolean _preserveEmptyAttributes = false;
+    /**
+     * If set the comparator is used to sort the attributes. If not set the order is preserved as it is.
+     */
+    private Comparator<String> _sortAttributeNamesBy = null;
 
     /**
      * Constructs a new output format with the default values.
@@ -796,6 +801,24 @@ public class OutputFormat {
     }
 
     /**
+     * Returns the {@link Comparator} used to apply an order to attribute names.
+     *
+     * @return
+     */
+    public Comparator<String> getSortAttributeNamesBy() {
+        return _sortAttributeNamesBy;
+    }
+
+    /**
+     * Sets the comparator to use for applying an order to attribute names. If set to null, the original order is preserved.
+     *
+     * @param sortAttributeNamesBy
+     */
+    public void setSortAttributeNamesBy(Comparator<String> sortAttributeNamesBy) {
+        this._sortAttributeNamesBy = sortAttributeNamesBy;
+    }
+
+    /**
      * Returns the last printable character based on the selected
      * encoding. Control characters and non-printable characters
      * are always printed as character references.

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/XMLSerializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/XMLSerializer.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/XMLSerializer.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/XMLSerializer.java Tue Mar 27 04:38:14 2018
@@ -35,6 +35,13 @@ package org.apache.jackrabbit.vault.util
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 import org.apache.jackrabbit.vault.util.xml.xerces.dom.DOMMessageFormatter;
 import org.apache.jackrabbit.vault.util.xml.xerces.util.NamespaceSupport;
@@ -51,6 +58,7 @@ import org.w3c.dom.Node;
 import org.xml.sax.AttributeList;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributeListImpl;
 import org.xml.sax.helpers.AttributesImpl;
 
 /**
@@ -315,6 +323,7 @@ public class XMLSerializer extends BaseM
             // separated with a space so the element can be broken on
             // multiple lines.
             if (attrs != null) {
+                attrs = sortAttributes(attrs);
                 // added by tripod@apache.org
                 boolean breakEachAttr = _format.getBreakEachAttribute()
                         && attrs.getLength() + (_prefixes == null ? 0 : _prefixes.size()) != 1
@@ -486,6 +495,7 @@ public class XMLSerializer extends BaseM
             // separated with a space so the element can be broken on
             // multiple lines.
             if (attrs != null) {
+                attrs = sortAttributes(attrs);
                 for (i = 0; i < attrs.getLength(); ++i) {
                     _printer.printSpace();
                     name = attrs.getName(i);
@@ -632,7 +642,6 @@ public class XMLSerializer extends BaseM
     protected void serializeElement(Element elem)
             throws IOException {
         Attr attr;
-        NamedNodeMap attrMap;
         int i;
         Node child;
         ElementState state;
@@ -689,14 +698,11 @@ public class XMLSerializer extends BaseM
         // This only happens in endElement().
         fPreserveSpace = state.preserveSpace;
 
-
-        int length = 0;
-        attrMap = null;
         // retrieve attributes
-        if (elem.hasAttributes()) {
-            attrMap = elem.getAttributes();
-            length = attrMap.getLength();
-        }
+        final Iterable<Node> attrMap = elem.hasAttributes()
+                ? sortAttributes(elem.getAttributes())
+                : Collections.<Node>emptyList();
+
 
         if (!fNamespaces) { // no namespace fixup should be performed
 
@@ -708,8 +714,8 @@ public class XMLSerializer extends BaseM
             // For each attribute print it's name and value as one part,
             // separated with a space so the element can be broken on
             // multiple lines.
-            for (i = 0; i < length; ++i) {
-                attr = (Attr) attrMap.item(i);
+            for (Node node: attrMap) {
+                attr = (Attr) node;
                 name = attr.getName();
                 value = attr.getValue();
                 if (value == null)
@@ -727,9 +733,8 @@ public class XMLSerializer extends BaseM
             // before attempting to fix element's namespace
             // ---------------------------------------
 
-            for (i = 0; i < length; i++) {
-
-                attr = (Attr) attrMap.item(i);
+            for (Node node: attrMap) {
+                attr = (Attr) node;
                 uri = attr.getNamespaceURI();
                 // check if attribute is a namespace decl
                 if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
@@ -885,9 +890,8 @@ public class XMLSerializer extends BaseM
             // check if prefix/namespace is correct the attributes
             // -----------------------------------------
 
-            for (i = 0; i < length; i++) {
-
-                attr = (Attr) attrMap.item(i);
+            for (Node node: attrMap) {
+                attr = (Attr) node;
                 value = attr.getValue();
                 name = attr.getNodeName();
 
@@ -1100,6 +1104,81 @@ public class XMLSerializer extends BaseM
         _printer.printText('"');
     }
 
+    private Iterable<Node> sortAttributes(final NamedNodeMap attributeList) {
+        final Comparator<String> cmp = _format.getSortAttributeNamesBy();
+
+        if (cmp == null) {
+            return new Iterable<Node>() {
+                @Override public Iterator<Node> iterator() {
+                    return new NamedNodeMapIterator(attributeList);
+                }
+            };
+        }
+
+        List<Node> attrs = new ArrayList<>(attributeList.getLength());
+
+        for (int i = 0, c = attributeList.getLength(); i < c; i++) {
+            attrs.add(attributeList.item(i));
+        }
+
+
+        Collections.sort(attrs, new Comparator<Node>() {
+            @Override public int compare(Node attr1, Node attr2) {
+                    return cmp.compare(getNameOf(attr1), getNameOf(attr2));
+                }
+
+            String getNameOf(Node attr) {
+                String uri = attr.getNamespaceURI();
+                return uri != null && uri.length() == 0 ? attr.getLocalName() : attr.getNodeName();
+            }
+        });
+
+        return attrs;
+    }
+
+    private Attributes sortAttributes(Attributes attributeList) {
+        Comparator<String> cmp = _format.getSortAttributeNamesBy();
+
+        if (cmp == null) {
+            return attributeList;
+        }
+
+        Map<String, Integer> attributes = new TreeMap<>(cmp);
+        for (int i = 0, c = attributeList.getLength(); i < c; i++) {
+            String qname = attributeList.getQName(i);
+            attributes.put(qname, i);
+        }
+
+        AttributesImpl sortedAttributes = new AttributesImpl();
+        for (Integer nextIndex: attributes.values()) {
+            sortedAttributes.addAttribute(attributeList.getURI(nextIndex), attributeList.getLocalName(nextIndex),
+                    attributeList.getQName(nextIndex), attributeList.getType(nextIndex), attributeList.getValue(nextIndex));
+        }
+
+        return sortedAttributes;
+    }
+
+    private AttributeList sortAttributes(AttributeList attributeList) {
+        Comparator<String> cmp = _format.getSortAttributeNamesBy();
+
+        if (cmp == null) {
+            return attributeList;
+        }
+
+        Map<String, Integer> attributes = new TreeMap<>(cmp);
+        for (int i = 0, c = attributeList.getLength(); i < c; i++) {
+            String name = attributeList.getName(i);
+            attributes.put(name, i);
+        }
+
+        AttributeListImpl sortedAttributes = new AttributeListImpl();
+        for (Integer nextIndex: attributes.values()) {
+            sortedAttributes.addAttribute(attributeList.getName(nextIndex), attributeList.getType(nextIndex),
+                    attributeList.getValue(nextIndex));
+        }
+
+        return sortedAttributes;
+    }
 
     /**
      * Prints attribute.
@@ -1444,6 +1523,29 @@ public class XMLSerializer extends BaseM
         return true;
     }
 
+    private static class NamedNodeMapIterator implements Iterator<Node> {
+
+        private final NamedNodeMap nodeMap;
+        private int index = 0;
+        private final int length;
+
+        NamedNodeMapIterator(NamedNodeMap nodeMap) {
+            this.nodeMap = nodeMap;
+            this.length = nodeMap.getLength();
+        }
+
+        @Override public boolean hasNext() {
+            return index < length;
+        }
+
+        @Override public Node next() {
+            return this.nodeMap.item(index++);
+        }
+
+        @Override public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
 }
 
 

Modified: jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/package-info.java?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/package-info.java (original)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/main/java/org/apache/jackrabbit/vault/util/xml/serialize/package-info.java Tue Mar 27 04:38:14 2018
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.4.0")
+@Version("2.5.0")
 package org.apache.jackrabbit.vault.util.xml.serialize;
 
 import org.osgi.annotation.versioning.Version;
\ No newline at end of file

Added: jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/DocViewFormatTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/DocViewFormatTest.java?rev=1827801&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/DocViewFormatTest.java (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/DocViewFormatTest.java Tue Mar 27 04:38:14 2018
@@ -0,0 +1,80 @@
+/*
+ * 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.fs.io;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DocViewFormatTest {
+
+    private File dir;
+    private File docViewFile;
+
+    @Before
+    public void setup() throws IOException {
+        String tempDir = System.getProperty("java.io.tmpdir");
+        dir = new File(tempDir + File.separator + "DocViewFormatTest" + new Date().toString());
+        assert dir.mkdir();
+        docViewFile = new File(dir.getPath() + File.separator + "malformed.xml");
+        assert docViewFile.createNewFile();
+
+        try (InputStream in = this.getClass().getClassLoader()
+                .getResourceAsStream("org/apache/jackrabbit/vault/fs/io/DocViewFormat/malformed.xml")) {
+            try (OutputStream out = new FileOutputStream(docViewFile)) {
+                IOUtils.copy(in, out);
+            }
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (!docViewFile.delete()) {
+            docViewFile.deleteOnExit();
+            dir.deleteOnExit();
+        } else {
+            if (!dir.delete()) {
+                dir.deleteOnExit();
+            }
+        }
+    }
+
+    @Test
+    public void testFormatting() throws IOException {
+        List<Pattern> patterns = Collections.singletonList(Pattern.compile(".+\\.xml"));
+        DocViewFormat format = new DocViewFormat();
+        assertFalse("malformed.xml is expected to be malformed", format.format(dir, patterns, true).isEmpty());
+        format.format(dir, patterns, false);
+        assertTrue("malformed.xml is expected to be formatted", format.format(dir, patterns, true).isEmpty());
+    }
+}

Added: jackrabbit/commons/filevault/trunk/vault-core/src/test/resources/org/apache/jackrabbit/vault/fs/io/DocViewFormat/malformed.xml
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-core/src/test/resources/org/apache/jackrabbit/vault/fs/io/DocViewFormat/malformed.xml?rev=1827801&view=auto
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-core/src/test/resources/org/apache/jackrabbit/vault/fs/io/DocViewFormat/malformed.xml (added)
+++ jackrabbit/commons/filevault/trunk/vault-core/src/test/resources/org/apache/jackrabbit/vault/fs/io/DocViewFormat/malformed.xml Tue Mar 27 04:38:14 2018
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
+          jcr:primaryType="nt:unstructured"
+          jcr:title="Test node">
+    <testChild jcr:primaryType="nt:unstructured"
+               foo="bar"/>
+</jcr:root>

Modified: jackrabbit/commons/filevault/trunk/vault-doc/src/site/markdown/usage.md
URL: http://svn.apache.org/viewvc/jackrabbit/commons/filevault/trunk/vault-doc/src/site/markdown/usage.md?rev=1827801&r1=1827800&r2=1827801&view=diff
==============================================================================
--- jackrabbit/commons/filevault/trunk/vault-doc/src/site/markdown/usage.md (original)
+++ jackrabbit/commons/filevault/trunk/vault-doc/src/site/markdown/usage.md Tue Mar 27 04:38:14 2018
@@ -27,7 +27,7 @@ The console tool is called `vlt` and has
     $vlt --help
     
     ----------------------------------------------------------------------------------------------
-    Jackrabbit FileVault [version 3.0.0] Copyright 2013 by Apache Software Foundation.
+    Jackrabbit FileVault [version 3.2.2] Copyright 2018 by Apache Software Foundation.
     See LICENSE.txt for more information.
     ----------------------------------------------------------------------------------------------
     Usage:
@@ -38,6 +38,8 @@ The console tool is called `vlt` and has
       -Xjcrlog <arg>           Extended JcrLog options (omit argument for help)
       -Xdavex <arg>            Extended JCR remoting options (omit argument for help)
       --credentials <arg>      The default credentials to use
+      --update-credentials     if present the credentials-to-host list is updated in the
+                               ~/.vault/auth.xml
       --config <arg>           The JcrFs config to use
       -v (--verbose)           verbose output
       -q (--quiet)             print as little as possible
@@ -62,6 +64,7 @@ The console tool is called `vlt` and has
       diff (di)                Display the differences between two paths.
       rcp                      Remote copy of repository content.
       sync                     Control vault sync service
+      format                   Formats vault docview files.
       console                  Run an interactive console
     ----------------------------------------------------------------------------------------------