org.apache.jackrabbit.oak.run.GenerateVersionInconsistencyReport 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.run;
import org.apache.jackrabbit.guava.common.io.Closer;
import org.apache.jackrabbit.oak.Oak;
import org.apache.jackrabbit.oak.api.ContentRepository;
import org.apache.jackrabbit.oak.api.ContentSession;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder;
import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
import org.apache.jackrabbit.oak.plugins.version.ReadOnlyVersionManager;
import org.apache.jackrabbit.oak.spi.blob.MemoryBlobStore;
import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
import org.jetbrains.annotations.NotNull;
import javax.jcr.SimpleCredentials;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* Generate a report with the list of affected versionHistory nodes containing
* empty version nodes or an incorrect primaryType.
* This is something that shouldn't happen when Oak is strictly used, but some
* external tools may introduce this kind of inconsistencies.
*/
public class GenerateVersionInconsistencyReport {
private static String VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage";
private static int PROGRESS_WAITING_TIME_MILLIS = 20 * 1000;
private volatile int count = 0;
private volatile int emptyNodesCount = 0;
private volatile int wrongNodesCount = 0;
private volatile boolean runningJob = false;
/**
* The tool is not integrated in the Oak-run Command framework as it's not
* intended for frequent or generic use.
* @param args At least 2 arguments are expected:
* - MongoDB URI
* - Repository admin username (optional)
* - Repository admin password
* @throws IOException
*/
public static void main(String args[]) throws IOException {
String adminUser = "admin";
String adminPwd = "";
if (args.length == 2) {
adminPwd = args[1];
} else if (args.length == 3) {
adminUser = args[1];
adminPwd = args[2];
} else {
System.out.println("Required arguments: [repository admin username] ");
System.out.println("If no username is specified, it will default to 'admin'.");
System.out.println("Examples:");
System.out.println(" \"mongodb://127.0.0.1:27017/database\" \"oak123456\"");
System.out.println(" \"mongodb://127.0.0.1:27017/database\" \"admin\" \"oak123456\"");
System.out.println();
System.exit(1);
}
GenerateVersionInconsistencyReport reportGenerator = new GenerateVersionInconsistencyReport();
reportGenerator.generateReport(adminUser, adminPwd, Arrays.copyOf(args, 1));
}
private void generateReport(String adminUser, String adminPwd, String...args) throws IOException {
Closer closer = Utils.createCloserWithShutdownHook();
String h = "mongodb://host:port/database|jdbc:...";
DocumentNodeStore dns = null;
try {
DocumentNodeStoreBuilder> builder = Utils.createDocumentMKBuilder(args, closer, h);
builder.setBlobStore(new MemoryBlobStore());
dns = builder.setClusterInvisible(true).setReadOnlyMode().build();
closer.register(Utils.asCloseable(dns));
Oak.OakDefaultComponents defs = new Oak.OakDefaultComponents();
Oak oak = new Oak(dns).with(new OpenSecurityProvider());
for (QueryIndexProvider qip : defs.queryIndexProviders()) {
oak.with(qip);
}
ContentRepository cr = oak.createContentRepository();
ContentSession contentSession = cr.login(new SimpleCredentials(adminUser, adminPwd.toCharArray()), null);
Root root = contentSession.getLatestRoot();
Tree tree = root.getTree(VERSION_STORAGE_PATH);
ReadOnlyNodeTypeManager readOnlyNodeTypeManager = ReadOnlyNodeTypeManager.getInstance(root, NamePathMapper.DEFAULT);
ReadOnlyVersionManager readOnlyVersionManager = new ReadOnlyVersionManager() {
@Override
protected @NotNull Tree getVersionStorage() { //jcr:system/jcr:versionStorage
return root.getTree(VERSION_STORAGE_PATH);
}
@Override
protected @NotNull Root getWorkspaceRoot() {
return root;
}
@Override
protected @NotNull ReadOnlyNodeTypeManager getNodeTypeManager() {
return readOnlyNodeTypeManager;
}
};
HashSet emptyNodes = new HashSet<>();
HashMap wrongNodes = new HashMap<>();
System.out.println("Searching for inconsistencies in version history...");
runningJob = true;
Thread progressThread = new Thread(new ProgressThread());
progressThread.start();
iterateAndFindEmptyAndWrongVersionNodes(tree, 3, readOnlyVersionManager, emptyNodes, wrongNodes);
runningJob = false;
System.out.println("Job Finished. Traversed: " + count + " nodes. Found " + emptyNodesCount + " empty and " + wrongNodesCount + " wrong nodes.");
System.out.println();
System.out.println("=== List 1: Empty nodes ===");
for (Tree node : emptyNodes) {
System.out.println(node.getPath());
}
System.out.println("Total empty nodes: " + emptyNodes.size());
System.out.println("=== End of empty nodes list ===");
System.out.println();
System.out.println("=== List 2: versionHistory nodes with wrong primaryType ===");
for (Map.Entry entry : wrongNodes.entrySet()) {
String nodePath = entry.getKey();
System.out.println(nodePath + " => " + entry.getValue());
}
System.out.println("Total wrong nodes: " + wrongNodes.size());
System.out.println("=== End of versionHistory nodes with wrong primaryType list ===");
dns.dispose();
} catch (Throwable e) {
e.printStackTrace();
} finally {
closer.close();
}
}
private void iterateAndFindEmptyAndWrongVersionNodes(Tree root, int level, ReadOnlyVersionManager readOnlyVersionManager, HashSet emptyNodes, HashMap wrongNodes) {
for (Tree treeNode : root.getChildren()) {
count++;
if (treeNode.getPropertyCount() == 0) {
emptyNodes.add(treeNode);
emptyNodesCount++;
}
if (level == 6) {
PropertyState prop = treeNode.getProperty("jcr:primaryType");
if (prop == null) {
wrongNodes.put(treeNode.getPath(), "is null");
wrongNodesCount++;
} else if (Type.STRING.equals(prop.getType())) {
wrongNodes.put(treeNode.getPath(), "not a String");
wrongNodesCount++;
} else if (!"nt:versionHistory".equals(prop.getValue(Type.STRING))) {
wrongNodes.put(treeNode.getPath(), "is set to " + prop.getValue(Type.STRING));
wrongNodesCount++;
}
}
iterateAndFindEmptyAndWrongVersionNodes(treeNode, level+1, readOnlyVersionManager, emptyNodes, wrongNodes);
}
}
private class ProgressThread implements Runnable {
public void run() {
try {
while (runningJob) {
System.out.println("Job is running... Traversed " + count + " nodes. Found " + emptyNodesCount + " empty and " + wrongNodesCount + " wrong nodes.");
Thread.sleep(PROGRESS_WAITING_TIME_MILLIS);
}
} catch (InterruptedException e) {
System.out.println("ProgressThread was interrupted");
e.printStackTrace();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy