blob: 0af9a59d8b366872fc5731724b0c08b2d64a7442 [file] [log] [blame]
/*
* 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.file.tooling;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
import static org.apache.jackrabbit.oak.json.JsonSerializer.DEFAULT_FILTER_EXPRESSION;
import static org.apache.jackrabbit.oak.segment.file.FileStoreBuilder.fileStoreBuilder;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import org.apache.jackrabbit.oak.json.BlobSerializer;
import org.apache.jackrabbit.oak.json.JsonSerializer;
import org.apache.jackrabbit.oak.segment.SegmentNodeState;
import org.apache.jackrabbit.oak.segment.spi.persistence.JournalFile;
import org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException;
import org.apache.jackrabbit.oak.segment.file.JournalEntry;
import org.apache.jackrabbit.oak.segment.file.JournalReader;
import org.apache.jackrabbit.oak.segment.file.ReadOnlyFileStore;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Utility for tracing a node back through the revision history.
*/
public class RevisionHistory {
private final ReadOnlyFileStore store;
/**
* Create a new instance for a {@link ReadOnlyFileStore} in the given {@code directory}.
*
* @param directory
* @throws IOException
*/
public RevisionHistory(@NotNull File directory) throws IOException, InvalidFileStoreVersionException {
this.store = fileStoreBuilder(checkNotNull(directory)).buildReadOnly();
}
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 Iterator<HistoryElement> getHistory(@NotNull JournalFile journal, @NotNull final String path)
throws IOException {
checkNotNull(path);
try (JournalReader journalReader = new JournalReader(checkNotNull(journal))) {
return Iterators.transform(journalReader,
new Function<JournalEntry, HistoryElement>() {
@NotNull @Override
public HistoryElement apply(JournalEntry entry) {
store.setRevision(entry.getRevision());
NodeState node = getNode(store.getHead(), path);
return new HistoryElement(entry.getRevision(), 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
*/
@NotNull
public String getRevision() {
return revision;
}
/**
* Node at given revision
* @return
*/
@Nullable
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);
}
}
}