org.apache.jackrabbit.oak.index.IndexDefinitionUpdater 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.index;
import com.google.common.base.Preconditions;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.apache.commons.io.FileUtils;
import org.apache.felix.inventory.Format;
import org.apache.jackrabbit.oak.api.Blob;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.jcr.Jcr;
import org.apache.jackrabbit.oak.plugins.index.IndexPathService;
import org.apache.jackrabbit.oak.plugins.index.inventory.IndexDefinitionPrinter;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
import org.apache.jackrabbit.oak.spi.state.*;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;
import static java.util.stream.StreamSupport.stream;
import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.apache.jackrabbit.oak.api.Type.*;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
public class IndexDefinitionUpdater {
private final NodeStore store;
private IndexDefinitionUpdater() {
store = new MemoryNodeStore();
// initialize as a JCR repo to register namespaces and create initial index structure (/oak:index for our use)
new Jcr(store).createRepository();
}
public static void main(String [] args) throws Exception {
OptionParser parser = new OptionParser();
parser.accepts("in", "input index definition file").withRequiredArg().required();
parser.accepts("out", "output index definition file").withRequiredArg();
parser.accepts("initializer", "implementation of RepositoryInitializer to update index definitions").withRequiredArg().required();
OptionSet optionSet = parser.parse(args);
Preconditions.checkArgument(optionSet.has("in"), "input index definition must be provided");
String inFilePath = optionSet.valueOf("in").toString();
Preconditions.checkArgument(optionSet.has("initializer"), "initializer class must be provided");
String initializerClassName = optionSet.valueOf("initializer").toString();
Class repoInitClazz = Class.forName(initializerClassName);
Object obj = repoInitClazz.newInstance();
Preconditions.checkArgument(obj instanceof RepositoryInitializer, repoInitClazz + " is not a RepositoryInitializer");
String outFilePath = optionSet.has("out") ? optionSet.valueOf("out").toString() : null;
new IndexDefinitionUpdater().process(inFilePath, outFilePath, (RepositoryInitializer)obj);
}
private void process(String inFilePath, String outFilePath, RepositoryInitializer initializer) throws Exception {
NodeState before = setupBaseState(inFilePath);
NodeState after = applyInitializer(before.builder(), initializer);
List reindexingDefPaths = getReindexIndexPaths();
List reindexingLuceneDefPaths = getLuceneIndexPaths(reindexingDefPaths);
if (outFilePath != null) {
writeReindexingLuceneDefs(outFilePath, reindexingLuceneDefPaths);
}
dumpIndexDiff(before, after, reindexingDefPaths);
}
private NodeState setupBaseState(String inFilePath) throws Exception {
org.apache.jackrabbit.oak.plugins.index.importer.IndexDefinitionUpdater indexDefinitionUpdater = new org.apache.jackrabbit.oak.plugins.index.importer.IndexDefinitionUpdater(new File(inFilePath));
NodeBuilder builder = store.getRoot().builder();
NodeBuilder oiBuilder = builder.getChildNode(INDEX_DEFINITIONS_NAME);
for (String indexPath : indexDefinitionUpdater.getIndexPaths()) {
oiBuilder.setChildNode(PathUtils.getName(indexPath), indexDefinitionUpdater.getIndexState(indexPath));
}
return merge(builder);
}
private NodeState applyInitializer(NodeBuilder builder, RepositoryInitializer initializer) throws CommitFailedException {
initializer.initialize(builder);
return merge(builder);
}
private void writeReindexingLuceneDefs(String outFilePath, List reindexingLuceneDefPaths) throws IOException {
IndexPathService indexPathService = () -> reindexingLuceneDefPaths;
IndexDefinitionPrinter printer = new IndexDefinitionPrinter(store, indexPathService);
try (PrintWriter pw = new PrintWriter(
new BufferedOutputStream(
FileUtils.openOutputStream(new File(outFilePath))))) {
printer.print(pw, Format.JSON, false);
}
}
private List getReindexIndexPaths() {
return stream(store.getRoot().getChildNode(INDEX_DEFINITIONS_NAME).getChildNodeEntries().spliterator(), false)
.filter(cne -> !cne.getNodeState().hasProperty(REINDEX_PROPERTY_NAME) || cne.getNodeState().getBoolean(REINDEX_PROPERTY_NAME))
.filter(cne -> cne.getNodeState().hasProperty(TYPE_PROPERTY_NAME))
.sorted(comparing(cne -> cne.getNodeState().getString(TYPE_PROPERTY_NAME)))
.map(cne -> "/" + INDEX_DEFINITIONS_NAME + "/" + cne.getName()).collect(Collectors.toList())
;
}
private List getLuceneIndexPaths(List indexDefPaths) {
NodeState root = store.getRoot();
return indexDefPaths.stream()
.filter(path -> "lucene".equals(NodeStateUtils.getNode(root, path).getString(TYPE_PROPERTY_NAME)))
.collect(Collectors.toList());
}
private NodeState merge(NodeBuilder builder) throws CommitFailedException {
store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
return store.getRoot();
}
private void dumpIndexDiff(NodeState beforeRoot, NodeState afterRoot, List reindexingLuceneDefPaths) {
try (PrintWriter pw = new PrintWriter(System.out)) {
for (String indexPath : reindexingLuceneDefPaths) {
NodeState before = NodeStateUtils.getNode(beforeRoot, indexPath);
NodeState after = NodeStateUtils.getNode(afterRoot, indexPath);
pw.println("");
pw.println("------ " + indexPath + " --------");
after.compareAgainstBaseState(before, new VisibleChangesDiff(pw));
}
}
}
static class VisibleChangesDiff implements NodeStateDiff {
private static final String INDENT = " ";
private static final Function BLOB_LENGTH = new Function() {
@Override
public String apply(Blob b) {
return safeGetLength(b);
}
private String safeGetLength(Blob b) {
try {
return byteCountToDisplaySize(b.length());
} catch (IllegalStateException e) {
// missing BlobStore probably
}
return "[N/A]";
}
};
private final PrintWriter pw;
private final String indent;
private boolean isRoot;
VisibleChangesDiff(PrintWriter pw) {
this(pw, INDENT);
isRoot = true;
}
private VisibleChangesDiff(PrintWriter pw, String indent) {
this.pw = pw;
this.indent = indent;
}
@Override
public boolean propertyAdded(PropertyState after) {
if (!after.getName().startsWith(":")) {
pw.println(indent + "+ " + toString(after));
}
return true;
}
@Override
public boolean propertyChanged(PropertyState before, PropertyState after) {
if (before.getName().equals("reindex") && isRoot) {
// don't care if reindex changed on top level
return true;
}
if (before.getName().startsWith(":")) {
return propertyAdded(after);
} else if (after.getName().startsWith(":")) {
return propertyDeleted(before);
} else {
pw.println(indent + "^ " + before.getName());
pw.println(indent + " - " + toString(before));
pw.println(indent + " + " + toString(after));
}
return true;
}
@Override
public boolean propertyDeleted(PropertyState before) {
if (!before.getName().startsWith(":")) {
pw.println(indent + "- " + toString(before));
}
return true;
}
@Override
public boolean childNodeAdded(String name, NodeState after) {
if (name.startsWith(":")) {
return true;
}
pw.println(indent + "+ " + name);
return after.compareAgainstBaseState(EMPTY_NODE, new VisibleChangesDiff(
pw, indent + INDENT));
}
@Override
public boolean childNodeChanged(String name, NodeState before, NodeState after) {
if (name.startsWith(":")) {
return true;
}
StringWriter sw = new StringWriter();
boolean ret;
try (PrintWriter subPw = new PrintWriter(sw)) {
ret = after.compareAgainstBaseState(before,
new VisibleChangesDiff(subPw, indent + INDENT));
}
if (sw.getBuffer().length() > 0) {
pw.println(indent + "^ " + name);
pw.print(sw.toString());
}
return ret;
}
@Override
public boolean childNodeDeleted(String name, NodeState before) {
if (name.startsWith(":")) {
return true;
}
pw.println(indent + "- " + name);
return MISSING_NODE.compareAgainstBaseState(before, new VisibleChangesDiff(pw, indent + INDENT));
}
private static String toString(PropertyState ps) {
StringBuilder val = new StringBuilder();
val.append(ps.getName()).append("<").append(ps.getType()).append(">");
if (ps.getType() == BINARY) {
String v = BLOB_LENGTH.apply(ps.getValue(BINARY));
val.append(" = {").append(v).append("}");
} else if (ps.getType() == BINARIES) {
String v = stream(ps.getValue(BINARIES).spliterator(), false)
.map(BLOB_LENGTH)
.collect(Collectors.toList()).toString();
val.append("[").append(ps.count()).append("] = ").append(v);
} else if (ps.isArray()) {
val.append("[").append(ps.count()).append("] = ").append(ps.getValue(STRINGS));
} else {
val.append(" = ").append(ps.getValue(STRING));
}
return ps.getName() + "<" + ps.getType() + ">" + val.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy