All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.segment.tool.Diff Maven / Gradle / Ivy

/*
 * 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.segment.tool;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.reverse;
import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
import static org.apache.jackrabbit.oak.segment.RecordId.fromString;
import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
import static org.apache.jackrabbit.oak.segment.tool.Utils.newBasicReadOnlyBlobStore;
import static org.apache.jackrabbit.oak.segment.tool.Utils.readRevisions;

import java.io.File;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;

import org.apache.jackrabbit.oak.segment.RecordId;
import org.apache.jackrabbit.oak.segment.SegmentIdProvider;
import org.apache.jackrabbit.oak.segment.SegmentNotFoundException;
import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore;
import org.apache.jackrabbit.oak.spi.state.NodeState;

/**
 * Shows the differences between two head states.
 */
public class Diff implements Runnable {

    /**
     * Create a builder for the {@link Diff} command.
     *
     * @return an instance of {@link Builder}.
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Collect options for the {@link Diff} command.
     */
    public static class Builder {

        private File path;

        private String interval;

        private boolean incremental;

        private File out;

        private String filter;

        private boolean ignoreMissingSegments;

        private Builder() {
            // Prevent external instantiation.
        }

        /**
         * The path to an existing segment store. This parameter is required.
         *
         * @param path the path to an existing segment store.
         * @return this builder.
         */
        public Builder withPath(File path) {
            this.path = checkNotNull(path);
            return this;
        }

        /**
         * The two node records to diff specified as a record ID interval. This
         * parameter is required.
         * 

* The interval is specified as two record IDs separated by two full * stops ({@code ..}). In example, {@code 333dc24d-438f-4cca-8b21-3ebf67c05856:12345..46116fda-7a72-4dbc-af88-a09322a7753a:67890}. * Instead of using a full record ID, it is possible to use the special * placeholder {@code head}. This placeholder is translated to the * record ID of the most recent head state. * * @param interval an interval between two node record IDs. * @return this builder. */ public Builder withInterval(String interval) { this.interval = checkNotNull(interval); return this; } /** * Set whether or not to perform an incremental diff of the specified * interval. An incremental diff shows every change between the two * records at every revision available to the segment store. This * parameter is not mandatory and defaults to {@code false}. * * @param incremental {@code true} to perform an incremental diff, * {@code false} otherwise. * @return this builder. */ public Builder withIncremental(boolean incremental) { this.incremental = incremental; return this; } /** * The file where the output of this command is stored. this parameter * is mandatory. * * @param file the output file. * @return this builder. */ public Builder withOutput(File file) { this.out = checkNotNull(file); return this; } /** * The path to a subtree. If specified, this parameter allows to * restrict the diff to the specified subtree. This parameter is not * mandatory and defaults to the entire tree. * * @param filter a path used as as filter for the resulting diff. * @return this builder. */ public Builder withFilter(String filter) { this.filter = checkNotNull(filter); return this; } /** * Whether to ignore exceptions caused by missing segments in the * segment store. This parameter is not mandatory and defaults to {@code * false}. * * @param ignoreMissingSegments {@code true} to ignore exceptions caused * by missing segments, {@code false} * otherwise. * @return this builder. */ public Builder withIgnoreMissingSegments(boolean ignoreMissingSegments) { this.ignoreMissingSegments = ignoreMissingSegments; return this; } /** * Create an executable version of the {@link Diff} command. * * @return an instance of {@link Runnable}. */ public Runnable build() { checkNotNull(path); checkNotNull(interval); checkNotNull(out); checkNotNull(filter); return new Diff(this); } } private final File path; private final String interval; private final boolean incremental; private final File out; private final String filter; private final boolean ignoreMissingSegments; private Diff(Builder builder) { this.path = builder.path; this.interval = builder.interval; this.incremental = builder.incremental; this.out = builder.out; this.filter = builder.filter; this.ignoreMissingSegments = builder.ignoreMissingSegments; } @Override public void run() { try { diff(); } catch (Exception e) { e.printStackTrace(); } } private void diff() throws Exception { System.out.println("Store " + path); System.out.println("Writing diff to " + out); String[] tokens = interval.trim().split("\\.\\."); if (tokens.length != 2) { System.out.println("Error parsing revision interval '" + interval + "'."); return; } try (ReadOnlyFileStore store = fileStoreBuilder(path).withBlobStore(newBasicReadOnlyBlobStore()).buildReadOnly()) { SegmentIdProvider idProvider = store.getSegmentIdProvider(); RecordId idL; try { if (tokens[0].equalsIgnoreCase("head")) { idL = store.getRevisions().getHead(); } else { idL = fromString(idProvider, tokens[0]); } } catch (IllegalArgumentException e) { System.out.println("Invalid left endpoint for interval " + interval); return; } RecordId idR; try { if (tokens[1].equalsIgnoreCase("head")) { idR = store.getRevisions().getHead(); } else { idR = fromString(idProvider, tokens[1]); } } catch (IllegalArgumentException e) { System.out.println("Invalid left endpoint for interval " + interval); return; } long start = System.currentTimeMillis(); try (PrintWriter pw = new PrintWriter(out)) { if (incremental) { List revs = readRevisions(path); System.out.println("Generating diff between " + idL + " and " + idR + " incrementally. Found " + revs.size() + " revisions."); int s = revs.indexOf(idL.toString10()); int e = revs.indexOf(idR.toString10()); if (s == -1 || e == -1) { System.out.println("Unable to match input revisions with FileStore."); return; } List revDiffs = revs.subList(Math.min(s, e), Math.max(s, e) + 1); if (s > e) { // reverse list revDiffs = reverse(revDiffs); } if (revDiffs.size() < 2) { System.out.println("Nothing to diff: " + revDiffs); return; } Iterator revDiffsIt = revDiffs.iterator(); RecordId idLt = fromString(idProvider, revDiffsIt.next()); while (revDiffsIt.hasNext()) { RecordId idRt = fromString(idProvider, revDiffsIt.next()); boolean good = diff(store, idLt, idRt, pw); idLt = idRt; if (!good && !ignoreMissingSegments) { break; } } } else { System.out.println("Generating diff between " + idL + " and " + idR); diff(store, idL, idR, pw); } } long dur = System.currentTimeMillis() - start; System.out.println("Finished in " + dur + " ms."); } } private boolean diff(ReadOnlyFileStore store, RecordId idL, RecordId idR, PrintWriter pw) { pw.println("rev " + idL + ".." + idR); try { NodeState before = store.getReader().readNode(idL).getChildNode("root"); NodeState after = store.getReader().readNode(idR).getChildNode("root"); for (String name : elements(filter)) { before = before.getChildNode(name); after = after.getChildNode(name); } after.compareAgainstBaseState(before, new PrintingDiff(pw, filter)); return true; } catch (SegmentNotFoundException ex) { System.out.println(ex.getMessage()); pw.println("#SNFE " + ex.getSegmentId()); return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy