blob: e3a8d8baa60710f00b4896a164757ce8957e2c9d [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.plugins.document;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.NotNull;
import static java.util.Objects.requireNonNull;
/**
* Consistency check on a {@code NodeDocument}.
*/
public class Consistency {
private final DocumentNodeState root;
private final NodeDocument document;
/**
* Creates a new consistency check for the given {@code NodeDocument}.
*
* @param root the current root node state to use for the check.
* @param document the document to check.
*/
public Consistency(@NotNull DocumentNodeState root,
@NotNull NodeDocument document) {
this.root = requireNonNull(root);
this.document = requireNonNull(document);
}
public void check(@NotNull DocumentNodeStore ns,
@NotNull Result collector) {
requireNonNull(ns);
requireNonNull(collector);
NodeState traversedState = root;
AbstractDocumentNodeState existingAncestorOrSelf = root;
for (String name : document.getPath().elements()) {
traversedState = traversedState.getChildNode(name);
if (traversedState instanceof DocumentNodeState) {
existingAncestorOrSelf = (DocumentNodeState) traversedState;
}
}
DocumentNodeState dns = document.getNodeAtRevision(ns, root.getRootRevision(), null);
if (traversedState.exists()) {
if (dns != null) {
if (!equalProperties(dns, traversedState)) {
// both exist, but they are not equal
identifyChanges(ns, traversedState, collector);
}
} else {
// exists when traversing to the node, getting node directly
// from document returned null
// document must have _deleted set to true after some _lastRev
identifyChanges(ns, traversedState, collector);
}
} else {
if (dns != null) {
// does not exist when traversing to node, but getting node
// directly returns it. node is orphaned
identifyChanges(ns, existingAncestorOrSelf, collector);
}
}
}
/**
* Callback interface for result of a consistency check.
*/
public interface Result {
/**
* Called when the consistency check identified an inconsistency. The
* reported {@code Revision} identifies the associated change with
* the inconsistency.
*
* @param revision the revision of the change.
*/
void inconsistent(Revision revision);
}
/**
* Compare the properties of both {@link NodeState}s and return {@code true}
* if they are equal, otherwise {@code false}.
*
* @param a a node state
* @param b another node state
* @return {@code true} if properties on both {@code NodeState}s are equal,
* {@code false} otherwise.
*/
private static boolean equalProperties(NodeState a, NodeState b) {
if (a.getPropertyCount() != b.getPropertyCount()) {
return false;
}
for (PropertyState property : a.getProperties()) {
if (!property.equals(b.getProperty(property.getName()))) {
return false;
}
}
return true;
}
private void identifyChanges(@NotNull RevisionContext context,
@NotNull NodeState state,
@NotNull Result result) {
if (state instanceof DocumentNodeState) {
DocumentNodeState traversedState = (DocumentNodeState) state;
// must not have any committed changes between lastRevision on
// traversed node state and root revision on this document
RevisionVector lastRevision = traversedState.getLastRevision();
RevisionVector rootRevision = traversedState.getRootRevision();
for (Revision r : document.getLocalCommitRoot().keySet()) {
if (!rootRevision.isRevisionNewer(r)
&& lastRevision.isRevisionNewer(r)
&& Utils.isCommitted(context.getCommitValue(r, document))) {
// committed change after lastRevision, but not newer
// than root revision
result.inconsistent(r);
}
}
}
}
}