You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2015/12/08 15:36:38 UTC

svn commit: r1718621 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/json/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/tooling/ oak-run/ oak-run/src/main/java/org/apache/jackrabbit/oak/run/

Author: mduerig
Date: Tue Dec  8 14:36:38 2015
New Revision: 1718621

URL: http://svn.apache.org/viewvc?rev=1718621&view=rev
Log:
OAK-3749: Implement tooling for tracing a node through the revision history
Initial implementation

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/tooling/RevisionHistory.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java
    jackrabbit/oak/trunk/oak-run/README.md
    jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java?rev=1718621&r1=1718620&r2=1718621&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/json/JsonSerializer.java Tue Dec  8 14:36:38 2015
@@ -41,9 +41,10 @@ import org.apache.jackrabbit.oak.spi.sta
  * Utility class for serializing node and property states to JSON.
  */
 public class JsonSerializer {
+    public static final String DEFAULT_FILTER_EXPRESSION =
+        "{\"properties\":[\"*\", \"-:childNodeCount\"]}";
 
-    private static final JsonFilter DEFAULT_FILTER =
-            new JsonFilter("{\"properties\":[\"*\", \"-:childNodeCount\"]}");
+    private static final JsonFilter DEFAULT_FILTER = new JsonFilter(DEFAULT_FILTER_EXPRESSION);
 
     private final JsopBuilder json;
 

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/tooling/RevisionHistory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/tooling/RevisionHistory.java?rev=1718621&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/tooling/RevisionHistory.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/tooling/RevisionHistory.java Tue Dec  8 14:36:38 2015
@@ -0,0 +1,161 @@
+/*
+ * 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.oak.plugins.segment.file.tooling;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
+import static org.apache.jackrabbit.oak.json.JsonSerializer.DEFAULT_FILTER_EXPRESSION;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.Function;
+import org.apache.jackrabbit.oak.json.BlobSerializer;
+import org.apache.jackrabbit.oak.json.JsonSerializer;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeState;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore.ReadOnlyStore;
+import org.apache.jackrabbit.oak.plugins.segment.file.JournalReader;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * Utility for tracing a node back through the revision history.
+ */
+public class RevisionHistory {
+    private final ReadOnlyStore store;
+
+    /**
+     * Create a new instance for a {@link FileStore} in the given {@code directory}.
+     *
+     * @param directory
+     * @throws IOException
+     */
+    public RevisionHistory(@Nonnull File directory) throws IOException {
+        this.store = new ReadOnlyStore(checkNotNull(directory));
+    }
+
+    private static NodeState getNode(SegmentNodeState root, String path) {
+        NodeState node = root;
+        for (String name : elements(path)) {
+            node = node.getChildNode(name);
+        }
+        return node;
+    }
+
+    /**
+     * Return the history of the node at the given {@code path} according to the passed
+     * {@code journal}.
+     *
+     * @param journal
+     * @param path
+     * @return
+     * @throws IOException
+     */
+    public Iterable<HistoryElement> getHistory(@Nonnull File journal, @Nonnull final String path)
+            throws IOException {
+        checkNotNull(path);
+        return transform(new JournalReader(checkNotNull(journal)),
+            new Function<String, HistoryElement>() {
+                @Nullable @Override
+                public HistoryElement apply(String revision) {
+                    store.setRevision(revision);
+                    NodeState node = getNode(store.getHead(), path);
+                    return new HistoryElement(revision, node);
+                }
+        });
+    }
+
+    /**
+     * Representation of a point in time for a given node.
+     */
+    public static final class HistoryElement {
+        private final String revision;
+        private final NodeState node;
+
+        HistoryElement(String revision, NodeState node) {
+            this.revision = revision;
+            this.node = node;
+        }
+
+        /**
+         * Revision of the node
+         * @return
+         */
+        @Nonnull
+        public String getRevision() {
+            return revision;
+        }
+
+        /**
+         * Node at given revision
+         * @return
+         */
+        @CheckForNull
+        public NodeState getNode() {
+            return node;
+        }
+
+        /**
+         * Serialise this element to JSON up to the given {@code depth}.
+         * @param depth
+         * @return
+         */
+        public String toString(int depth) {
+            JsonSerializer json = new JsonSerializer(depth, 0, Integer.MAX_VALUE,
+                DEFAULT_FILTER_EXPRESSION, new BlobSerializer());
+            json.serialize(node);
+            return revision + "=" + json;
+        }
+
+        /**
+         * @return  {@code toString(0)}
+         */
+        @Override
+        public String toString() {
+            return toString(0);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (other == null || getClass() != other.getClass()) {
+                return false;
+            }
+
+            HistoryElement that = (HistoryElement) other;
+            return revision.equals(that.revision) &&
+                (node == null ? that.node == null : node.equals(that.node));
+
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * revision.hashCode() +
+                (node != null ? node.hashCode() : 0);
+        }
+    }
+}

Modified: jackrabbit/oak/trunk/oak-run/README.md
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/README.md?rev=1718621&r1=1718620&r2=1718621&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/README.md (original)
+++ jackrabbit/oak/trunk/oak-run/README.md Tue Dec  8 14:36:38 2015
@@ -15,6 +15,7 @@ The following runmodes are currently ava
     * console     : Start an interactive console.
     * explore     : Starts a GUI browser based on java swing.
     * graph       : Export the segment graph of a segment store to a file.
+    * history     : Trace the history of a node
     * check       : Check the FileStore for inconsistencies
     * primary     : Run a TarMK Cold Standby primary instance
     * standby     : Run a TarMK Cold Standby standby instance
@@ -120,6 +121,23 @@ a negative offset translating all timest
                        given)
     --output <File>  Output file (default: segments.gdf)
 
+History
+-------
+
+Trace the history of a node backward through the revision history.
+
+    $ java -jar oak-run-*.jar history [File] <options>
+
+    [File] -- Path to segment store (required)
+
+    Option             Description
+    ------             -----------
+    --depth <Integer>  Depth up to which to dump node states
+                         (default: 0)
+    --journal          journal file (default: journal.log)
+    --path             Path for which to trace the history
+                         (default: /)
+
 Check
 -----
 

Modified: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java?rev=1718621&r1=1718620&r2=1718621&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java (original)
+++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java Tue Dec  8 14:36:38 2015
@@ -113,6 +113,8 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.segment.file.FileStore;
 import org.apache.jackrabbit.oak.plugins.segment.file.FileStore.ReadOnlyStore;
 import org.apache.jackrabbit.oak.plugins.segment.file.JournalReader;
+import org.apache.jackrabbit.oak.plugins.segment.file.tooling.RevisionHistory;
+import org.apache.jackrabbit.oak.plugins.segment.file.tooling.RevisionHistory.HistoryElement;
 import org.apache.jackrabbit.oak.plugins.segment.standby.client.StandbyClient;
 import org.apache.jackrabbit.oak.plugins.segment.standby.server.StandbyServer;
 import org.apache.jackrabbit.oak.plugins.tika.TextExtractorMain;
@@ -175,6 +177,9 @@ public final class Main {
             case GRAPH:
                 graph(args);
                 break;
+            case HISTORY:
+                history(args);
+                break;
             case CHECK:
                 check(args);
                 break;
@@ -882,6 +887,43 @@ public final class Main {
         checkConsistency(dir, journalFileName, fullTraversal, debugLevel, binLen);
     }
 
+    private static void history(String[] args) throws IOException {
+        OptionParser parser = new OptionParser();
+        OptionSpec<File> directoryArg = parser.nonOptions(
+            "Path to segment store (required)").ofType(File.class);
+        OptionSpec<String> journalArg = parser.accepts(
+            "journal", "journal file").withRequiredArg().ofType(String.class)
+            .defaultsTo("journal.log");
+        OptionSpec<String> pathArg = parser.accepts(
+            "path", "Path for which to trace the history").withRequiredArg().ofType(String.class)
+            .defaultsTo("/");
+        OptionSpec<Integer> depthArg = parser.accepts(
+            "depth", "Depth up to which to dump node states").withRequiredArg().ofType(Integer.class)
+            .defaultsTo(0);
+        OptionSet options = parser.parse(args);
+
+        File directory = directoryArg.value(options);
+        if (directory == null) {
+            System.err.println("Trace the history of a path. Usage: history [File] <options>");
+            parser.printHelpOn(System.err);
+            System.exit(-1);
+        }
+        if (!isValidFileStore(directory.getPath())) {
+            System.err.println("Invalid FileStore directory " + args[0]);
+            System.exit(1);
+        }
+
+        String path = pathArg.value(options);
+        int depth = depthArg.value(options);
+        String journalName = journalArg.value(options);
+        File journal = new File(directory, journalName);
+
+        Iterable<HistoryElement> history = new RevisionHistory(directory).getHistory(journal, path);
+        for (HistoryElement historyElement : history) {
+            System.out.println(historyElement.toString(depth));
+        }
+    }
+
     private static void debugTarFile(FileStore store, String[] args) {
         File root = new File(args[0]);
         for (int i = 1; i < args.length; i++) {
@@ -1259,6 +1301,7 @@ public final class Main {
         CONSOLE("console"),
         DEBUG("debug"),
         GRAPH("graph"),
+        HISTORY("history"),
         CHECK("check"),
         COMPACT("compact"),
         SERVER("server"),