This is an automated email from the ASF dual-hosted git repository.
thomasm pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 21c2414424 OAK-12010 (Remove misplaced files)
21c2414424 is described below
commit 21c2414424e680efa00498521c2aca8e9aa1fc3d
Author: Thomas Mueller <[email protected]>
AuthorDate: Tue Feb 10 16:03:27 2026 +0100
OAK-12010 (Remove misplaced files)
---
oak-core/DiffIndex.java | 242 ------------
oak-core/DiffIndexMerger.java | 833 ------------------------------------------
oak-core/DiffIndexTest.java | 362 ------------------
oak-core/MergeTest.java | 437 ----------------------
4 files changed, 1874 deletions(-)
diff --git a/oak-core/DiffIndex.java b/oak-core/DiffIndex.java
deleted file mode 100644
index 6ee23643ea..0000000000
--- a/oak-core/DiffIndex.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * 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.index.diff;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Comparator;
-
-import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.commons.json.JsonObject;
-import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
-import org.apache.jackrabbit.oak.plugins.index.IndexName;
-import org.apache.jackrabbit.oak.plugins.tree.TreeConstants;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeStore;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Processing of diff indexes, that is nodes under "/oak:index/diff.index". A
- * diff index contains differences to existing indexes, and possibly new
- * (custom) indexes in the form of JSON. These changes can then be merged
- * (applied) to the index definitions. This allows to simplify index
management,
- * because it allows to modify (add, update) indexes in a simple way.
- */
-public class DiffIndex {
-
- private static final Logger LOG = LoggerFactory.getLogger(DiffIndex.class);
-
- /**
- * Apply changes to the index definitions. That means merge the index diff
with
- * the existing indexes, creating new index versions. It might also mean to
- * remove old (merged) indexes if the diff no longer contains them.
- *
- * @param store the node store
- * @param indexDefinitions the /oak:index node
- */
- public static void applyDiffIndexChanges(NodeStore store, NodeBuilder
indexDefinitions) {
- JsonObject newImageLuceneDefinitions = null;
- for (String diffIndex : new String[] { DiffIndexMerger.DIFF_INDEX,
DiffIndexMerger.DIFF_INDEX_OPTIMIZER }) {
- if (!indexDefinitions.hasChildNode(diffIndex)) {
- continue;
- }
- NodeBuilder diffIndexDefinition =
indexDefinitions.child(diffIndex);
- NodeBuilder diffContent =
diffIndexDefinition.getChildNode("diff.json").getChildNode("jcr:content");
- if (!diffContent.exists()) {
- continue;
- }
- PropertyState lastMod =
diffContent.getProperty("jcr:lastModified");
- if (lastMod == null) {
- continue;
- }
- String modified = lastMod.getValue(Type.DATE);
- PropertyState lastProcessed =
diffContent.getProperty(":lastProcessed");
- if (lastProcessed != null) {
- if (modified.equals(lastProcessed.getValue(Type.STRING))) {
- // already processed
- continue;
- }
- }
- // store now, so a change is only processed once
- diffContent.setProperty(":lastProcessed", modified);
- PropertyState jcrData = diffContent.getProperty("jcr:data");
- String diff = tryReadString(jcrData);
- if (diff == null) {
- continue;
- }
- try {
- JsonObject diffObj = JsonObject.fromJson("{\"diff\": " + diff
+ "}", true);
- diffIndexDefinition.removeProperty("error");
- if (newImageLuceneDefinitions == null) {
- newImageLuceneDefinitions = new JsonObject();
- }
- newImageLuceneDefinitions.getChildren().put("/oak:index/" +
diffIndex, diffObj);
- } catch (Exception e) {
- String message = "Error parsing diff.index";
- LOG.warn(message + ": {}", e.getMessage(), e);
- diffIndexDefinition.setProperty("error", message + ": " +
e.getMessage());
- }
- }
- if (newImageLuceneDefinitions == null) {
- // not a valid diff index, or already processed
- return;
- }
- LOG.info("Processing a new diff.index with node store {}", store);
- JsonObject repositoryDefinitions =
RootIndexesListService.getRootIndexDefinitions(indexDefinitions);
- LOG.debug("Index list {}", repositoryDefinitions.toString());
- try {
- DiffIndexMerger.instance().merge(newImageLuceneDefinitions,
repositoryDefinitions, store);
- for (String indexPath :
newImageLuceneDefinitions.getChildren().keySet()) {
- if (indexPath.startsWith("/oak:index/" +
DiffIndexMerger.DIFF_INDEX)) {
- continue;
- }
- JsonObject newDef =
newImageLuceneDefinitions.getChildren().get(indexPath);
- String indexName = PathUtils.getName(indexPath);
- JsonNodeBuilder.addOrReplace(indexDefinitions, store,
indexName, IndexConstants.INDEX_DEFINITIONS_NODE_TYPE, newDef.toString());
- updateNodetypeIndexForPath(indexDefinitions, indexName, true);
- disableOrRemoveOldVersions(indexDefinitions, indexPath,
indexName);
- }
- removeDisabledMergedIndexes(indexDefinitions);
- sortIndexes(indexDefinitions);
- } catch (Exception e) {
- LOG.warn("Error merging diff.index: {}", e.getMessage(), e);
- NodeBuilder diffIndexDefinition =
indexDefinitions.child(DiffIndexMerger.DIFF_INDEX);
- diffIndexDefinition.setProperty("error", e.getMessage());
- }
- }
-
- /**
- * Try to read a text from the (binary) jcr:data property. Edge cases such
as
- * "property does not exist" and IO exceptions (blob not found) do not
throw an
- * exception (IO exceptions are logged).
- *
- * @param jcrData the "jcr:data" property
- * @return the string, or null if reading fails
- */
- public static String tryReadString(PropertyState jcrData) {
- if (jcrData == null) {
- return null;
- }
- InputStream in = jcrData.getValue(Type.BINARY).getNewStream();
- try {
- return new String(in.readAllBytes(), StandardCharsets.UTF_8);
- } catch (IOException e) {
- LOG.warn("Can not read jcr:data", e);
- return null;
- }
- }
-
- private static void sortIndexes(NodeBuilder builder) {
- ArrayList<String> list = new ArrayList<>();
- for (String child : builder.getChildNodeNames()) {
- list.add(child);
- }
- list.sort(Comparator.naturalOrder());
- builder.setProperty(TreeConstants.OAK_CHILD_ORDER, list, Type.NAMES);
- }
-
- private static void removeDisabledMergedIndexes(NodeBuilder definitions) {
- ArrayList<String> toRemove = new ArrayList<>();
- for (String child : definitions.getChildNodeNames()) {
- if (!definitions.getChildNode(child).hasProperty("mergeChecksum"))
{
- continue;
- }
- if
("disabled".equals(definitions.getChildNode(child).getString("type"))) {
- toRemove.add(child);
- }
- }
- for (String r : toRemove) {
- LOG.info("Removing disabled index {}", r);
- definitions.child(r).remove();
- updateNodetypeIndexForPath(definitions, r, false);
- }
- }
-
- /**
- * Try to remove or disable old version of merged indexes, if there are
any.
- *
- * @param definitions the builder for /oak:index
- * @param indexPath the path
- * @param keep which index name (which version) to retain
- */
- private static void disableOrRemoveOldVersions(NodeBuilder definitions,
String indexPath, String keep) {
- String indexName = indexPath;
- if (indexPath.startsWith("/oak:index/")) {
- indexName = indexPath.substring("/oak:index/".length());
- }
- String baseName = IndexName.parse(indexName).getBaseName();
- ArrayList<String> toRemove = new ArrayList<>();
- for (String child : definitions.getChildNodeNames()) {
- if (child.equals(keep) || child.indexOf("-custom-") < 0) {
- // the one to keep, or not a customized or custom index
- continue;
- }
- String childBaseName = IndexName.parse(child).getBaseName();
- if (baseName.equals(childBaseName)) {
- if (indexName.equals(child)) {
- if
(!"disabled".equals(definitions.getChildNode(indexName).getString("type"))) {
- continue;
- }
- }
- toRemove.add(child);
- }
- }
- for (String r : toRemove) {
- LOG.info("Removing old index " + r);
- definitions.child(r).remove();
- updateNodetypeIndexForPath(definitions, r, false);
- }
- }
-
- private static void updateNodetypeIndexForPath(NodeBuilder
indexDefinitions,
- String indexName, boolean add) {
- LOG.info("nodetype index update add={} name={}", add, indexName);
- if (!indexDefinitions.hasChildNode("nodetype")) {
- return;
- }
- NodeBuilder nodetypeIndex = indexDefinitions.getChildNode("nodetype");
- NodeBuilder indexContent = nodetypeIndex.child(":index");
- String key = URLEncoder.encode("oak:QueryIndexDefinition",
StandardCharsets.UTF_8);
- String path = "/oak:index/" + indexName;
- if (add) {
- // insert entry
- NodeBuilder builder = indexContent.child(key);
- for (String name : PathUtils.elements(path)) {
- builder = builder.child(name);
- }
- LOG.info("nodetype index match");
- builder.setProperty("match", true);
- } else {
- // remove entry (for deleted indexes)
- NodeBuilder builder = indexContent.getChildNode(key);
- for (String name : PathUtils.elements(path)) {
- builder = builder.getChildNode(name);
- }
- if (builder.exists()) {
- LOG.info("nodetype index remove");
- builder.removeProperty("match");
- }
- }
- }
-
-}
diff --git a/oak-core/DiffIndexMerger.java b/oak-core/DiffIndexMerger.java
deleted file mode 100644
index f6d1ebc9c4..0000000000
--- a/oak-core/DiffIndexMerger.java
+++ /dev/null
@@ -1,833 +0,0 @@
-/*
- * 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.index.diff;
-
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-import org.apache.jackrabbit.oak.commons.StringUtils;
-import org.apache.jackrabbit.oak.commons.json.JsonObject;
-import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
-import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
-import org.apache.jackrabbit.oak.json.Base64BlobSerializer;
-import org.apache.jackrabbit.oak.json.JsonSerializer;
-import org.apache.jackrabbit.oak.plugins.index.IndexName;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
-import org.apache.jackrabbit.oak.spi.state.NodeStore;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Index definition merge utility that uses the "diff" mode.
- */
-public class DiffIndexMerger {
-
- private final static Logger LOG =
LoggerFactory.getLogger(DiffIndexMerger.class);
-
- public final static String DIFF_INDEX = "diff.index";
- public final static String DIFF_INDEX_OPTIMIZER = "diff.index.optimizer";
-
- private final static String MERGE_INFO = "This index was auto-merged. See
also https://oak-indexing.github.io/oakTools/simplified.html";
-
- // the list of unsupported included paths, e.g. "/apps,/libs"
- // by default all paths are supported
- private final static String[] UNSUPPORTED_INCLUDED_PATHS =
System.getProperty("oak.diffIndex.unsupportedPaths", "").split(",");
-
- // in case a custom index is removed, whether a dummy index is created
- private final static boolean DELETE_CREATES_DUMMY =
Boolean.getBoolean("oak.diffIndex.deleteCreatesDummy");
-
- // in case a customization was removed, create a copy of the OOTB index
- private final static boolean DELETE_COPIES_OOTB =
Boolean.getBoolean("oak.diffIndex.deleteCopiesOOTB");
-
- // whether to log at info level
- private final static boolean LOG_AT_INFO_LEVEL =
Boolean.getBoolean("oak.diffIndex.logAtInfoLevel");
-
- private final String[] unsupportedIncludedPaths;
- private final boolean deleteCreatesDummyIndex;
- private final boolean deleteCopiesOutOfTheBoxIndex;
- private final boolean logAtInfoLevel;
-
- static final DiffIndexMerger INSTANCE = new
DiffIndexMerger(UNSUPPORTED_INCLUDED_PATHS,
- DELETE_CREATES_DUMMY, DELETE_COPIES_OOTB, LOG_AT_INFO_LEVEL);
-
- public static DiffIndexMerger instance() {
- return INSTANCE;
- }
-
- DiffIndexMerger(String[] unsupportedIncludedPaths,
- boolean deleteCreatesDummyIndex, boolean
deleteCopiesOutOfTheBoxIndex,
- boolean logAtInfoLevel) {
- this.unsupportedIncludedPaths = unsupportedIncludedPaths;
- this.deleteCreatesDummyIndex = deleteCreatesDummyIndex;
- this.deleteCopiesOutOfTheBoxIndex = deleteCopiesOutOfTheBoxIndex;
- this.logAtInfoLevel = logAtInfoLevel;
- }
-
- /**
- * If there is a diff index, that is an index with prefix "diff.", then
try to merge it.
- *
- * @param newImageLuceneDefinitions
- * the new indexes
- * (input and output)
- * @param repositoryDefinitions
- * the indexes in the writable repository
- * (input)
- * @param repositoryNodeStore
- */
- public void merge(JsonObject newImageLuceneDefinitions, JsonObject
repositoryDefinitions, NodeStore repositoryNodeStore) {
- // combine all definitions into one object
- JsonObject combined = new JsonObject();
-
- // index definitions in the repository
- combined.getChildren().putAll(repositoryDefinitions.getChildren());
-
- // read the diff.index.optimizer explicitly,
- // because it's a not a regular index definition,
- // and so in the repositoryDefinitions
- if (repositoryNodeStore != null) {
- Map<String, JsonObject> diffInRepo =
readDiffIndex(repositoryNodeStore, DIFF_INDEX_OPTIMIZER);
- combined.getChildren().putAll(diffInRepo);
- }
-
- // overwrite with the provided definitions (if any)
- combined.getChildren().putAll(newImageLuceneDefinitions.getChildren());
-
- // check if there "diff.index" or "diff.index.optimizer"
- boolean found = combined.getChildren().containsKey("/oak:index/" +
DIFF_INDEX)
- || combined.getChildren().containsKey("/oak:index/" +
DIFF_INDEX_OPTIMIZER);
- if (!found) {
- // early exit, so that the risk of merging the PR
- // is very small for customers that do not use this
- log("No 'diff.index' definition");
- return;
- }
- mergeDiff(newImageLuceneDefinitions, combined);
- }
-
- /**
- * If there is a diff index (hardcoded node "/oak:index/diff.index" or
- * "/oak:index/diff.index.optimizer"), then iterate over all entries and
create new
- * (merged) versions if needed.
- *
- * @param newImageLuceneDefinitions
- * the new Lucene definitions
- * (input + output)
- * @param combined
- * the definitions in the repository,
- * including the one in the customer repo and new ones
- * (input)
- * @return whether a new version of an index was added
- */
- boolean mergeDiff(JsonObject newImageLuceneDefinitions, JsonObject
combined) {
- // iterate again, this time process
-
- // collect the diff index(es)
- HashMap<String, JsonObject> toProcess = new HashMap<>();
- tryExtractDiffIndex(combined, "/oak:index/" + DIFF_INDEX, toProcess);
- tryExtractDiffIndex(combined, "/oak:index/" + DIFF_INDEX_OPTIMIZER,
toProcess);
- // if the diff index exists, but doesn't contain some of the previous
indexes
- // (indexes with mergeInfo), then we need to disable those (using
/dummy includedPath)
- extractExistingMergedIndexes(combined, toProcess);
- if (toProcess.isEmpty()) {
- log("No diff index definitions found.");
- return false;
- }
- boolean hasChanges = false;
- for (Entry<String, JsonObject> e : toProcess.entrySet()) {
- String key = e.getKey();
- JsonObject value = e.getValue();
- if (key.startsWith("/oak:index/")) {
- LOG.warn("The key should contains just the index name, without
the '/oak:index' prefix for key {}", key);
- key = key.substring("/oak:index/".length());
- }
- log("Processing {}", key);
- hasChanges |= processMerge(key, value, newImageLuceneDefinitions,
combined);
- }
- return hasChanges;
- }
-
- /**
- * Extract a "diff.index" from the set of index definitions (if found),
and if
- * found, store the nested entries in the target map, merging them with
previous
- * entries if found.
- *
- * The diff.index may either have a file (a "jcr:content" child node with a
- * "jcr:data" property), or a "diff" JSON object. For customers (in the git
- * repository), the file is much easier to construct, but when running the
- * indexing job, the nested JSON is much easier.
- *
- * @param indexDefs the set of index definitions (may be empty)
- * @param name the name of the diff.index (either diff.index or
- * diff.index.optimizer)
- * @param target the target map of diff.index definitions
- * @return the error message trying to parse the JSON file, or null
- */
- public static String tryExtractDiffIndex(JsonObject indexDefs, String
name, HashMap<String, JsonObject> target) {
- JsonObject diffIndex = indexDefs.getChildren().get(name);
- if (diffIndex == null) {
- return null;
- }
- // extract either the file, or the nested json
- JsonObject file = diffIndex.getChildren().get("diff.json");
- JsonObject diff;
- if (file != null) {
- // file
- JsonObject jcrContent = file.getChildren().get("jcr:content");
- if (jcrContent == null) {
- String message = "jcr:content child node is missing in
diff.json";
- LOG.warn(message);
- return message;
- }
- String jcrData = JsonNodeBuilder.oakStringValue(jcrContent,
"jcr:data");
- try {
- diff = JsonObject.fromJson(jcrData, true);
- } catch (Exception e) {
- LOG.warn("Illegal Json, ignoring: {}", jcrData, e);
- String message = "Illegal Json, ignoring: " + e.getMessage();
- return message;
- }
- } else {
- // nested json
- diff = diffIndex.getChildren().get("diff");
- }
- // store, if not empty
- if (diff != null) {
- for (Entry<String, JsonObject> e : diff.getChildren().entrySet()) {
- String key = e.getKey();
- target.put(key, mergeDiffs(target.get(key), e.getValue()));
- }
- }
- return null;
- }
-
- /**
- * Extract the indexes with a "mergeInfo" property and store them in the
target
- * object. This is needed so that indexes that were removed from the
index.diff
- * are detected (a new version is needed in this case with includedPaths
- * "/dummy").
- *
- * @param indexDefs the index definitions in the repository
- * @param target the target map of "diff.index" definitions. for each
entry
- * found, an empty object is added
- */
- private static void extractExistingMergedIndexes(JsonObject indexDefs,
HashMap<String, JsonObject> target) {
- for (Entry<String, JsonObject> e : indexDefs.getChildren().entrySet())
{
- String key = e.getKey();
- JsonObject value = e.getValue();
- if (key.indexOf("-custom-") < 0 ||
!value.getProperties().containsKey("mergeInfo")) {
- continue;
- }
- String baseName =
IndexName.parse(key.substring("/oak:index/".length())).getBaseName();
- if (!target.containsKey(baseName)) {
- // if there is no entry yet for this key,
- // add a new empty object
- target.put(baseName, new JsonObject());
- }
- }
- }
-
- /**
- * Merge diff from "diff.index" and "diff.index.optimizer".
- * The customer can define a diff (stored in "diff.index")
- * and someone else (or the optimizer) can define one (stored in
"diff.index.optimizer").
- *
- * @param a the first diff
- * @param b the second diff (overwrites entries in a)
- * @return the merged entry
- */
- public static JsonObject mergeDiffs(JsonObject a, JsonObject b) {
- if (a == null) {
- return b;
- } else if (b == null) {
- return a;
- }
- JsonObject result = JsonObject.fromJson(a.toString(), true);
- result.getProperties().putAll(b.getProperties());
- HashSet<String> both = new HashSet<>(a.getChildren().keySet());
- both.addAll(b.getChildren().keySet());
- for (String k : both) {
- result.getChildren().put(k, mergeDiffs(a.getChildren().get(k),
b.getChildren().get(k)));
- }
- return result;
- }
-
- /**
- * Merge using the diff definition.
- *
- * If the latest customized index already matches, then
- * newImageLuceneDefinitions will remain as is. Otherwise, a new customized
- * index is added, with a "mergeInfo" property.
- *
- * Existing properties are never changed; only new properties/children are
- * added.
- *
- * @param indexName the name, eg. "damAssetLucene"
- * @param indexDiff the diff with the new properties
- * @param newImageLuceneDefinitions the new Lucene definitions (input +
output)
- * @param combined the definitions in the repository,
including
- * the one in the customer repo and new
ones
- * (input)
- * @return whether a new version of an index was added
- */
- public boolean processMerge(String indexName, JsonObject indexDiff,
JsonObject newImageLuceneDefinitions, JsonObject combined) {
- // extract the latest product index (eg. damAssetLucene-12)
- // and customized index (eg. damAssetLucene-12-custom-3) - if any
- IndexName latestProduct = null;
- String latestProductKey = null;
- IndexName latestCustomized = null;
- String latestCustomizedKey = null;
- String prefix = "/oak:index/";
- for (String key : combined.getChildren().keySet()) {
- IndexName name = IndexName.parse(key.substring(prefix.length()));
- if (!name.isVersioned()) {
- log("Ignoring unversioned index {}", name);
- continue;
- }
- if (!name.getBaseName().equals(indexName)) {
- continue;
- }
- boolean isCustom = key.indexOf("-custom-") >= 0;
- if (isCustom) {
- if (latestCustomized == null ||
- name.compareTo(latestCustomized) > 0) {
- latestCustomized = name;
- latestCustomizedKey = key;
- }
- } else {
- if (latestProduct == null ||
- name.compareTo(latestProduct) > 0) {
- latestProduct = name;
- latestProductKey = key;
- }
- }
- }
- log("Latest product: {}", latestProductKey);
- log("Latest customized: {}", latestCustomizedKey);
- if (latestProduct == null) {
- if (indexName.indexOf('.') >= 0) {
- // a fully custom index needs to contains a dot
- log("Fully custom index {}", indexName);
- } else {
- log("No product version for {}", indexName);
- return false;
- }
- }
- JsonObject latestProductIndex =
combined.getChildren().get(latestProductKey);
- String[] includedPaths;
- if (latestProductIndex == null) {
- if (indexDiff.getProperties().isEmpty() &&
indexDiff.getChildren().isEmpty()) {
- // there is no customization (any more), which means a dummy
index may be needed
- log("No customization for {}", indexName);
- } else {
- includedPaths = JsonNodeBuilder.oakStringArrayValue(indexDiff,
"includedPaths");
- if (includesUnsupportedPaths(includedPaths)) {
- LOG.warn("New custom index {} is not supported because it
contains an unsupported path ({})",
- indexName,
Arrays.toString(unsupportedIncludedPaths));
- return false;
- }
- }
- } else {
- includedPaths =
JsonNodeBuilder.oakStringArrayValue(latestProductIndex, "includedPaths");
- if (includesUnsupportedPaths(includedPaths)) {
- LOG.warn("Customizing index {} is not supported because it
contains an unsupported path ({})",
- latestProductKey,
Arrays.toString(unsupportedIncludedPaths));
- return false;
- }
- }
-
- // merge
- JsonObject merged = null;
- if (indexDiff == null) {
- // no diff definition: use to the OOTB index
- if (latestCustomized == null) {
- log("Only a product index found, nothing to do");
- return false;
- }
- merged = latestProductIndex;
- } else {
- merged = processMerge(latestProductIndex, indexDiff);
- }
-
- // compare to the latest version of the this index
- JsonObject latestIndexVersion = new JsonObject();
- if (latestCustomized == null) {
- latestIndexVersion = latestProductIndex;
- } else {
- latestIndexVersion =
combined.getChildren().get(latestCustomizedKey);
- }
- JsonObject mergedDef = cleanedAndNormalized(switchToLucene(merged));
- // compute merge checksum for later, but do not yet add
- String mergeChecksum = computeMergeChecksum(mergedDef);
- // get the merge checksum before cleaning (cleaning removes it) - if
available
- String key;
- if (latestIndexVersion == null) {
- // new index
- key = prefix + indexName + "-1-custom-1";
- } else {
- String latestMergeChecksum =
JsonNodeBuilder.oakStringValue(latestIndexVersion, "mergeChecksum");
- JsonObject latestDef =
cleanedAndNormalized(switchToLucene(latestIndexVersion));
- if (isSameIgnorePropertyOrder(mergedDef, latestDef)) {
- // normal case: no change
- // (even if checksums do not match: checksums might be missing
or manipulated)
- log("Latest index matches");
- if (latestMergeChecksum != null &&
!latestMergeChecksum.equals(mergeChecksum)) {
- LOG.warn("Indexes do match, but checksums do not. Possibly
checksum was changed: {} vs {}", latestMergeChecksum, mergeChecksum);
- LOG.warn("latest: {}\nmerged: {}", latestDef, mergedDef);
- }
- return false;
- }
- if (latestMergeChecksum != null &&
latestMergeChecksum.equals(mergeChecksum)) {
- // checksum matches, but data does not match
- // could be eg. due to numbers formatting issues (-0.0 vs 0.0,
0.001 vs 1e-3)
- // but unexpected because we do not normally have such cases
- LOG.warn("Indexes do not match, but checksums match. Possible
normalization issue.");
- LOG.warn("Index: {}, latest: {}\nmerged: {}", indexName,
latestDef, mergedDef);
- // if checksums match, we consider it a match
- return false;
- }
- LOG.info("Indexes do not match, with");
- LOG.info("Index: {}, latest: {}\nmerged: {}", indexName,
latestDef, mergedDef);
- // a new merged index definition
- if (latestProduct == null) {
- // fully custom index: increment version
- key = prefix + indexName +
- "-" + latestCustomized.getProductVersion() +
- "-custom-" + (latestCustomized.getCustomerVersion() +
1);
- } else {
- // customized OOTB index: use the latest product as the base
- key = prefix + indexName +
- "-" + latestProduct.getProductVersion() +
- "-custom-";
- if (latestCustomized != null) {
- key += (latestCustomized.getCustomerVersion() + 1);
- } else {
- key += "1";
- }
- }
- }
- merged.getProperties().put("mergeInfo",
JsopBuilder.encode(MERGE_INFO));
- merged.getProperties().put("mergeChecksum",
JsopBuilder.encode(mergeChecksum));
- merged.getProperties().put("merges", "[" +
JsopBuilder.encode("/oak:index/" + indexName) + "]");
- merged.getProperties().remove("reindexCount");
- merged.getProperties().remove("reindex");
- if (!deleteCopiesOutOfTheBoxIndex &&
indexDiff.toString().equals("{}")) {
- merged.getProperties().put("type", "\"disabled\"");
- merged.getProperties().put("mergeComment", "\"This index is
superseeded and can be removed\"");
- }
- newImageLuceneDefinitions.getChildren().put(key, merged);
- return true;
- }
-
- /**
- * Check whether the includedPaths covers unsupported paths,
- * if there are any unsupported path (eg. "/apps" or "/libs").
- * In this case, simplified index management is not supported.
- *
- * @param includedPaths the includedPaths list
- * @return true if any unsupported path is included
- */
- public boolean includesUnsupportedPaths(String[] includedPaths) {
- if (unsupportedIncludedPaths.length == 1 &&
"".equals(unsupportedIncludedPaths[0])) {
- // set to an empty string
- return false;
- }
- if (includedPaths == null) {
- // not set means all entries
- return true;
- }
- for (String path : includedPaths) {
- if ("/".equals(path)) {
- // all
- return true;
- }
- for (String unsupported : unsupportedIncludedPaths) {
- if (unsupported.isEmpty()) {
- continue;
- }
- if (path.equals(unsupported) || path.startsWith(unsupported +
"/")) {
- // includedPaths matches, or starts with an unsupported
path
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Compute the SHA-256 checksum of the JSON object. This is useful to
detect
- * that the JSON object was not "significantly" changed, even if stored
- * somewhere and later read again. Insignificant changes include: rounding
of
- * floating point numbers, re-ordering properties, things like that.
Without the
- * checksum, we would risk creating a new version of a customized index
each
- * time the indexing job is run, even thought the customer didn't change
- * anything.
- *
- * @param json the input
- * @return the SHA-256 checksum
- */
- private static String computeMergeChecksum(JsonObject json) {
- byte[] bytes = json.toString().getBytes(StandardCharsets.UTF_8);
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- return StringUtils.convertBytesToHex(md.digest(bytes));
- } catch (NoSuchAlgorithmException e) {
- // SHA-256 is guaranteed to be available in standard Java platforms
- throw new RuntimeException("SHA-256 algorithm not available", e);
- }
- }
-
- /**
- * Switch the index from type "elasticsearch" to "lucene", if needed. This
will
- * also replace all properties that have an "...@lucene" version.
- *
- * This is needed because we want to merge only the "lucene" version, to
- * simplify the merging logic. (The switch to the "elasticsearch" version
- * happens later).
- *
- * @param indexDef the index definition (is not changed by this method)
- * @return the lucene version (a new JSON object)
- */
- public static JsonObject switchToLucene(JsonObject indexDef) {
- JsonObject obj = JsonObject.fromJson(indexDef.toString(), true);
- String type = JsonNodeBuilder.oakStringValue(obj, "type");
- if (type == null || !"elasticsearch".equals(type) ) {
- return obj;
- }
- switchToLuceneChildren(obj);
- return obj;
- }
-
- public static void switchToLuceneChildren(JsonObject indexDef) {
- // clone the keys to avoid ConcurrentModificationException
- for (String p : new ArrayList<>(indexDef.getProperties().keySet())) {
- if (!p.endsWith("@lucene")) {
- continue;
- }
- String v = indexDef.getProperties().remove(p);
- indexDef.getProperties().put(p.substring(0, p.length() -
"@lucene".length()), v);
- }
- for (String c : indexDef.getChildren().keySet()) {
- JsonObject co = indexDef.getChildren().get(c);
- switchToLuceneChildren(co);
- }
- }
-
- /**
- * Convert the JSON object to a new object, where index definition
- * properties that are unimportant for comparison are removed.
- * Example of important properties are "reindex", "refresh", "seed" etc.
- * The order of properties is not relevant (but the order of children is).
- *
- * @param obj the input (is not changed by the method)
- * @return a new JSON object
- */
- public static JsonObject cleanedAndNormalized(JsonObject obj) {
- obj = JsonObject.fromJson(obj.toString(), true);
- obj.getProperties().remove(":version");
- obj.getProperties().remove(":nameSeed");
- obj.getProperties().remove(":mappingVersion");
- obj.getProperties().remove("refresh");
- obj.getProperties().remove("reindexCount");
- obj.getProperties().remove("reindex");
- obj.getProperties().remove("seed");
- obj.getProperties().remove("merges");
- obj.getProperties().remove("mergeInfo");
- obj.getProperties().remove("mergeChecksum");
- for (String p : new ArrayList<>(obj.getProperties().keySet())) {
- if (p.endsWith("@lucene")) {
- obj.getProperties().remove(p);
- } else if (p.endsWith("@elasticsearch")) {
- obj.getProperties().remove(p);
- } else {
- // remove "str:", "nam:", etc if needed
- String v = obj.getProperties().get(p);
- String v2 = normalizeOakString(v);
- if (!v2.equals(v)) {
- obj.getProperties().put(p, v2);
- }
- }
- }
- removeUUIDs(obj);
- for (Entry<String, JsonObject> e : obj.getChildren().entrySet()) {
- obj.getChildren().put(e.getKey(),
cleanedAndNormalized(e.getValue()));
- }
- // re-build the properties in alphabetical order
- // (sorting the child nodes would be incorrect however, as order is
significant here)
- TreeMap<String, String> props = new TreeMap<>(obj.getProperties());
- obj.getProperties().clear();
- for (Entry<String, String> e : props.entrySet()) {
- obj.getProperties().put(e.getKey(), e.getValue());
- }
- return obj;
- }
-
- /**
- * "Normalize" a JSON string value. Remove any "nam:" and "dat:" and "str:"
- * prefix in the value, because customers won't use them normally. (We
want the
- * diff to be as simple as possible).
- *
- * @param value the value (including double quotes; eg. "str:value")
- * @return the normalized value (including double quotes)
- */
- private static String normalizeOakString(String value) {
- if (value == null || !value.startsWith("\"")) {
- // ignore numbers
- return value;
- }
- value = JsopTokenizer.decodeQuoted(value);
- if (value.startsWith("str:") || value.startsWith("nam:") ||
value.startsWith("dat:")) {
- value = value.substring("str:".length());
- }
- return JsopBuilder.encode(value);
- }
-
- /**
- * Remove all "jcr:uuid" properties (including those in children), because
the
- * values might conflict. (new uuids are added later when needed).
- *
- * @param obj the JSON object where uuids will be removed.
- */
- private static void removeUUIDs(JsonObject obj) {
- obj.getProperties().remove("jcr:uuid");
- for (JsonObject c : obj.getChildren().values()) {
- removeUUIDs(c);
- }
- }
-
- /**
- * Merge a product index with a diff. If the product index is null, then
the
- * diff needs to contain a complete custom index definition.
- *
- * @param productIndex the product index definition, or null if none
- * @param diff the diff (from the diff.index definition)
- * @return the index definition of the merged index
- */
- public JsonObject processMerge(JsonObject productIndex, JsonObject diff) {
- JsonObject result;
- if (productIndex == null) {
- // fully custom index
- result = new JsonObject(true);
- } else {
- result = JsonObject.fromJson(productIndex.toString(), true);
- }
- mergeInto("", diff, result);
- addPrimaryType("", result);
- return result;
- }
-
- /**
- * Add primary type properties where needed. For the top-level index
definition,
- * this is "oak:QueryIndexDefinition", and "nt:unstructured" elsewhere.
- *
- * @param path the path (so we can call the method recursively)
- * @param json the JSON object (is changed if needed)
- */
- private static void addPrimaryType(String path, JsonObject json) {
- // all nodes need to have a node type;
- // the index definition itself (at root level) is
"oak:QueryIndexDefinition",
- // and all other nodes are "nt:unstructured"
- if (!json.getProperties().containsKey("jcr:primaryType")) {
- // all nodes need to have a primary type,
- // otherwise index import will fail
- String nodeType;
- if (path.isEmpty()) {
- nodeType = "oak:QueryIndexDefinition";
- } else {
- nodeType = "nt:unstructured";
- }
- String nodeTypeValue = "nam:" + nodeType;
- json.getProperties().put("jcr:primaryType",
JsopBuilder.encode(nodeTypeValue));
- }
- for (Entry<String, JsonObject> e : json.getChildren().entrySet()) {
- addPrimaryType(path + "/" + e.getKey(), e.getValue());
- }
- }
-
- /**
- * Merge a JSON diff into a target index definition.
- *
- * @param path the path
- * @param diff the diff (what to merge)
- * @param target where to merge into
- */
- private void mergeInto(String path, JsonObject diff, JsonObject target) {
- for (String p : diff.getProperties().keySet()) {
- if (path.isEmpty()) {
- if ("jcr:primaryType".equals(p)) {
- continue;
- }
- }
- if (target.getProperties().containsKey(p)) {
- // we do not currently allow to overwrite most existing
properties
- if (p.equals("boost")) {
- // allow overwriting the boost value
- LOG.info("Overwrite property {} value at {}", p, path);
- target.getProperties().put(p, diff.getProperties().get(p));
- } else {
- LOG.warn("Ignoring existing property {} at {}", p, path);
- }
- } else {
- target.getProperties().put(p, diff.getProperties().get(p));
- }
- }
- for (String c : diff.getChildren().keySet()) {
- String targetChildName = c;
- if (!target.getChildren().containsKey(c)) {
- if (path.endsWith("/properties")) {
- // search for a property with the same "name" value
- String propertyName =
diff.getChildren().get(c).getProperties().get("name");
- if (propertyName != null) {
- propertyName =
JsonNodeBuilder.oakStringValue(propertyName);
- String c2 = getChildWithKeyValuePair(target, "name",
propertyName);
- if (c2 != null) {
- targetChildName = c2;
- }
- }
- // search for a property with the same "function" value
- String function =
diff.getChildren().get(c).getProperties().get("function");
- if (function != null) {
- function = JsonNodeBuilder.oakStringValue(function);
- String c2 = getChildWithKeyValuePair(target,
"function", function);
- if (c2 != null) {
- targetChildName = c2;
- }
- }
- }
- if (targetChildName.equals(c)) {
- // only create the child (properties are added below)
- target.getChildren().put(c, new JsonObject());
- }
- }
- mergeInto(path + "/" + targetChildName, diff.getChildren().get(c),
target.getChildren().get(targetChildName));
- }
- if (target.getProperties().isEmpty() &&
target.getChildren().isEmpty()) {
- if (deleteCreatesDummyIndex) {
- // dummy index
- target.getProperties().put("async", "\"async\"");
- target.getProperties().put("includedPaths", "\"/dummy\"");
- target.getProperties().put("queryPaths", "\"/dummy\"");
- target.getProperties().put("type", "\"lucene\"");
- JsopBuilder buff = new JsopBuilder();
- buff.object().
- key("properties").object().
- key("dummy").object().
- key("name").value("dummy").
- key("propertyIndex").value(true).
- endObject().
- endObject().
- endObject();
- JsonObject indexRules = JsonObject.fromJson(buff.toString(),
true);
- target.getChildren().put("indexRules", indexRules);
- } else {
- target.getProperties().put("type", "\"disabled\"");
- }
- }
- }
-
- public static String getChildWithKeyValuePair(JsonObject obj, String key,
String value) {
- for(Entry<String, JsonObject> c : obj.getChildren().entrySet()) {
- String v2 = c.getValue().getProperties().get(key);
- if (v2 == null) {
- continue;
- }
- v2 = JsonNodeBuilder.oakStringValue(v2);
- if (value.equals(v2)) {
- return c.getKey();
- }
- }
- return null;
- }
-
- /**
- * Compare two JSON object, ignoring the order of properties. (The order of
- * children is however significant).
- *
- * This is done in addition to the checksum comparison, because the in
theory
- * the customer might change the checksum (it is not read-only as read-only
- * values are not supported). We do not rely on the comparison, but if
comparison
- * and checksum comparison do not match, we log a warning.
- *
- * @param a the first object
- * @param b the second object
- * @return true if the keys and values are equal
- */
- public boolean isSameIgnorePropertyOrder(JsonObject a, JsonObject b) {
- if (!a.getChildren().keySet().equals(b.getChildren().keySet())) {
- log("Child (order) difference: {} vs {}",
- a.getChildren().keySet(), b.getChildren().keySet());
- return false;
- }
- for (String k : a.getChildren().keySet()) {
- if (!isSameIgnorePropertyOrder(
- a.getChildren().get(k), b.getChildren().get(k))) {
- return false;
- }
- }
- TreeMap<String, String> pa = new TreeMap<>(a.getProperties());
- TreeMap<String, String> pb = new TreeMap<>(b.getProperties());
- if (!pa.toString().equals(pb.toString())) {
- log("Property value difference: {} vs {}", pa.toString(),
pb.toString());
- }
- return pa.toString().equals(pb.toString());
- }
-
- /**
- * Read a diff.index from the repository, if it exists.
- * This is needed because the build-transform job doesn't have this
- * data: it is only available in the writeable repository.
- *
- * @param repositoryNodeStore the node store
- * @return a map, possibly with a single entry with this key
- */
- public Map<String, JsonObject> readDiffIndex(NodeStore
repositoryNodeStore, String name) {
- HashMap<String, JsonObject> map = new HashMap<>();
- NodeState root = repositoryNodeStore.getRoot();
- String indexPath = "/oak:index/" + name;
- NodeState idxState = NodeStateUtils.getNode(root, indexPath);
- log("Searching index {}: found={}", indexPath, idxState.exists());
- if (!idxState.exists()) {
- return map;
- }
- JsopBuilder builder = new JsopBuilder();
- String filter = "{\"properties\":[\"*\",
\"-:childOrder\"],\"nodes\":[\"*\", \"-:*\"]}";
- JsonSerializer serializer = new JsonSerializer(builder, filter, new
Base64BlobSerializer());
- serializer.serialize(idxState);
- JsonObject jsonObj = JsonObject.fromJson(builder.toString(), true);
- jsonObj = cleanedAndNormalized(jsonObj);
- log("Found {}", jsonObj.toString());
- map.put(indexPath, jsonObj);
- return map;
- }
-
- private void log(String format, Object... arguments) {
- if (logAtInfoLevel) {
- LOG.info(format, arguments);
- } else {
- LOG.debug(format, arguments);
- }
- }
-
-}
diff --git a/oak-core/DiffIndexTest.java b/oak-core/DiffIndexTest.java
deleted file mode 100644
index e5ae279a20..0000000000
--- a/oak-core/DiffIndexTest.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * 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.index.diff;
-
-import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT;
-import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
-import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Optional;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.JcrConstants;
-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.api.Type;
-import org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState;
-import org.apache.jackrabbit.oak.commons.json.JsonObject;
-import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate;
-import org.apache.jackrabbit.oak.plugins.index.CompositeIndexEditorProvider;
-import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
-import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
-import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider;
-import
org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider;
-import org.apache.jackrabbit.oak.plugins.index.optimizer.DiffIndexUpdater;
-import
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
-import
org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider;
-import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
-import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
-import org.apache.jackrabbit.oak.spi.commit.EditorHook;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeStore;
-import org.junit.Test;
-import org.mockito.MockedStatic;
-
-/**
- * Tests for DiffIndex functionality.
- */
-public class DiffIndexTest {
-
- @Test
- public void testFindMatchingIndexName() throws IOException {
- String indexJson = "{\n" +
- " \"index\": {\n" +
- " \"compatVersion\": 2,\n" +
- " \"async\": \"async\",\n" +
- " \"queryPaths\": [\"/content/dam/test\"],\n" +
- " \"includedPaths\": [\"/content/dam/test\"],\n" +
- " \"jcr:primaryType\": \"nam:oak:QueryIndexDefinition\",\n" +
- " \"evaluatePathRestrictions\": true,\n" +
- " \"type\": \"lucene\",\n" +
- " \"tags\": [\"fragments\"],\n" +
- " \"indexRules\": {\n" +
- " \"jcr:primaryType\": \"nam:nt:unstructured\",\n" +
- " \"dam:Asset\": {\n" +
- " \"jcr:primaryType\": \"nam:nt:unstructured\",\n" +
- " \"properties\": {\n" +
- " \"jcr:primaryType\": \"nam:nt:unstructured\",\n" +
- " \"title\": {\n" +
- " \"name\": \"str:jcr:title\",\n" +
- " \"propertyIndex\": true,\n" +
- " \"jcr:primaryType\": \"nam:nt:unstructured\"\n" +
- " }\n" +
- " }\n" +
- " }\n" +
- " }\n" +
- " }\n" +
- "}";
-
- try (MockedStatic<?> mockedStatic =
mockStatic(RootIndexesListService.class)) {
- NodeStore store = mock(NodeStore.class);
-
- String indexesJsonString;
-
- try (InputStream stream =
getClass().getResourceAsStream("/org/apache/jackrabbit/oak/plugins/index/diff/indexes.json"))
{
- indexesJsonString = IOUtils.toString(stream,
StandardCharsets.UTF_8);
- }
-
- mockedStatic.when(() ->
RootIndexesListService.getRootIndexDefinitions(eq(store), anyString()))
- .thenReturn(JsonObject.fromJson(indexesJsonString, true));
-
- Optional<String> matchingIndexName =
DiffIndexUpdater.findMatchingIndexName(store, indexJson);
-
- assertTrue(matchingIndexName.isPresent());
- }
- }
-
- @Test
- public void listIndexes() {
- NodeStore store = new MemoryNodeStore(INITIAL_CONTENT);
- JsonObject indexDefs =
RootIndexesListService.getRootIndexDefinitions(store, "property");
- // expect at least one index
- assertFalse(indexDefs.getChildren().isEmpty());
- }
-
- @Test
- public void tryReadStringNull() {
- assertNull(DiffIndex.tryReadString(null));
- }
-
- @Test
- public void tryReadStringValidContent() {
- String content = "Hello, World!";
- PropertyState prop = BinaryPropertyState.binaryProperty("jcr:data",
- content.getBytes(StandardCharsets.UTF_8));
- assertEquals(content, DiffIndex.tryReadString(prop));
- }
-
- @Test
- public void tryReadStringEmpty() {
- PropertyState prop = BinaryPropertyState.binaryProperty("jcr:data",
new byte[0]);
- assertEquals("", DiffIndex.tryReadString(prop));
- }
-
- @Test
- public void tryReadStringJsonContent() {
- String content = "{ \"key\": \"value\", \"array\": [1, 2, 3] }";
- PropertyState prop = BinaryPropertyState.binaryProperty("jcr:data",
- content.getBytes(StandardCharsets.UTF_8));
- assertEquals(content, DiffIndex.tryReadString(prop));
- }
-
- @Test
- public void tryReadStringIOException() throws IOException {
- PropertyState prop = mock(PropertyState.class);
- Blob blob = mock(Blob.class);
- InputStream failingStream = new InputStream() {
- @Override
- public int read() throws IOException {
- throw new IOException("Simulated read failure");
- }
- @Override
- public byte[] readAllBytes() throws IOException {
- throw new IOException("Simulated read failure");
- }
- };
- when(prop.getValue(Type.BINARY)).thenReturn(blob);
- when(blob.getNewStream()).thenReturn(failingStream);
-
- // Should return null (not throw exception)
- assertNull(DiffIndex.tryReadString(prop));
- }
-
- @Test
- public void testDiffIndexUpdate() throws Exception {
- // Create a memory node store
- NodeStore store = new MemoryNodeStore(INITIAL_CONTENT);
-
- storeDiff(store, "2026-01-01T00:00:00.000Z", ""
- + "{ \"acme.testIndex\": {\n"
- + " \"async\": [ \"async\", \"nrt\" ],\n"
- + " \"compatVersion\": 2,\n"
- + " \"evaluatePathRestrictions\": true,\n"
- + " \"includedPaths\": [ \"/content/dam\" ],\n"
- + " \"jcr:primaryType\":
\"oak:QueryIndexDefinition\",\n"
- + " \"queryPaths\": [ \"/content/dam\" ],\n"
- + " \"selectionPolicy\": \"tag\",\n"
- + " \"tags\": [ \"abc\" ],\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nt:unstructured\",\n"
- + " \"dam:Asset\": {\n"
- + " \"jcr:primaryType\": \"nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\":
\"nt:unstructured\",\n"
- + " \"created\": {\n"
- + " \"jcr:primaryType\":
\"nt:unstructured\",\n"
- + " \"name\": \"str:jcr:created\",\n"
- + " \"ordered\": true,\n"
- + " \"propertyIndex\": true,\n"
- + " \"type\": \"Date\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " } }");
-
- JsonObject repositoryDefinitions =
RootIndexesListService.getRootIndexDefinitions(store, "lucene");
- assertSameJson("{\n"
- + " \"/oak:index/acme.testIndex-1-custom-1\": {\n"
- + " \"compatVersion\": 2,\n"
- + " \"async\": [\"async\", \"nrt\"],\n"
- + " \"evaluatePathRestrictions\": true,\n"
- + " \"mergeChecksum\":
\"34e7f7f0eb480ea781317b56134bc85fc59ed97031d95f518fdcff230aec28a2\",\n"
- + " \"mergeInfo\": \"This index was auto-merged. See also
https://oak-indexing.github.io/oakTools/simplified.html\",\n"
- + " \"selectionPolicy\": \"tag\",\n"
- + " \"queryPaths\": [\"/content/dam\"],\n"
- + " \"includedPaths\": [\"/content/dam\"],\n"
- + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"tags\": [\"abc\"],\n"
- + " \"merges\": [\"/oak:index/acme.testIndex\"],\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"dam:Asset\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"created\": {\n"
- + " \"ordered\": true,\n"
- + " \"name\": \"str:jcr:created\",\n"
- + " \"propertyIndex\": true,\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"type\": \"Date\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", repositoryDefinitions.toString());
-
- storeDiff(store, "2026-01-01T00:00:00.001Z", ""
- + "{ \"acme.testIndex\": {\n"
- + " \"async\": [ \"async\", \"nrt\" ],\n"
- + " \"compatVersion\": 2,\n"
- + " \"evaluatePathRestrictions\": true,\n"
- + " \"includedPaths\": [ \"/content/dam\" ],\n"
- + " \"jcr:primaryType\":
\"oak:QueryIndexDefinition\",\n"
- + " \"queryPaths\": [ \"/content/dam\" ],\n"
- + " \"selectionPolicy\": \"tag\",\n"
- + " \"tags\": [ \"abc\" ],\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nt:unstructured\",\n"
- + " \"dam:Asset\": {\n"
- + " \"jcr:primaryType\": \"nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\":
\"nt:unstructured\",\n"
- + " \"created\": {\n"
- + " \"jcr:primaryType\":
\"nt:unstructured\",\n"
- + " \"name\": \"str:jcr:created\",\n"
- + " \"propertyIndex\": true\n"
- + " },\n"
- + " \"modified\": {\n"
- + " \"jcr:primaryType\":
\"nt:unstructured\",\n"
- + " \"name\": \"str:jcr:modified\",\n"
- + " \"propertyIndex\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " } }");
-
- repositoryDefinitions =
RootIndexesListService.getRootIndexDefinitions(store, "lucene");
- assertSameJson("{\n"
- + " \"/oak:index/acme.testIndex-1-custom-2\": {\n"
- + " \"compatVersion\": 2,\n"
- + " \"async\": [\"async\", \"nrt\"],\n"
- + " \"mergeChecksum\":
\"41df9c87e4d4fca446aed3f55e6d188304a2cb49bae442b75403dc23a89b266f\",\n"
- + " \"mergeInfo\": \"This index was auto-merged. See also
https://oak-indexing.github.io/oakTools/simplified.html\",\n"
- + " \"selectionPolicy\": \"tag\",\n"
- + " \"queryPaths\": [\"/content/dam\"],\n"
- + " \"includedPaths\": [\"/content/dam\"],\n"
- + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
- + " \"evaluatePathRestrictions\": true,\n"
- + " \"type\": \"lucene\",\n"
- + " \"tags\": [\"abc\"],\n"
- + " \"merges\": [\"/oak:index/acme.testIndex\"],\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"dam:Asset\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"created\": {\n"
- + " \"name\": \"str:jcr:created\",\n"
- + " \"propertyIndex\": true,\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
- + " },\n"
- + " \"modified\": {\n"
- + " \"name\": \"str:jcr:modified\",\n"
- + " \"propertyIndex\": true,\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", repositoryDefinitions.toString());
-
- storeDiff(store, "2026-01-01T00:00:00.002Z", ""
- + "{}");
-
- repositoryDefinitions =
RootIndexesListService.getRootIndexDefinitions(store, "lucene");
- assertSameJson("{}", repositoryDefinitions.toString());
- }
-
- private void assertSameJson(String a, String b) {
- JsonObject ja = JsonObject.fromJson(a, true);
- JsonObject jb = JsonObject.fromJson(b, true);
- if (!DiffIndexMerger.instance().isSameIgnorePropertyOrder(ja, jb)) {
- assertEquals(a, b);
- }
- }
-
- private void storeDiff(NodeStore store, String timestamp, String json)
throws CommitFailedException {
- // Get the root builder
- NodeBuilder builder = store.getRoot().builder();
-
- List<IndexEditorProvider> indexEditors = List.of(
- new ReferenceEditorProvider(), new
PropertyIndexEditorProvider(), new NodeCounterEditorProvider());
- IndexEditorProvider provider =
CompositeIndexEditorProvider.compose(indexEditors);
- EditorHook hook = new EditorHook(new IndexUpdateProvider(provider));
-
- // Create the index definition at /oak:index/diff.index
- NodeBuilder indexDefs = builder.child(INDEX_DEFINITIONS_NAME);
- NodeBuilder diffIndex = indexDefs.child("diff.index");
-
- // Set index properties
- diffIndex.setProperty("jcr:primaryType",
IndexConstants.INDEX_DEFINITIONS_NODE_TYPE, Type.NAME);
- diffIndex.setProperty(TYPE_PROPERTY_NAME, "disabled");
-
- // Create the diff.json child node with primary type nt:file
- NodeBuilder diffJson = diffIndex.child("diff.json");
- diffJson.setProperty(JcrConstants.JCR_PRIMARYTYPE,
JcrConstants.NT_FILE, Type.NAME);
-
- // Create jcr:content child node (required for nt:file) with empty text
- NodeBuilder content = diffJson.child(JcrConstants.JCR_CONTENT);
- content.setProperty(JcrConstants.JCR_LASTMODIFIED, timestamp);
- content.setProperty(JcrConstants.JCR_PRIMARYTYPE,
JcrConstants.NT_RESOURCE, Type.NAME);
-
- content.setProperty("jcr:data", json);
-
- // Merge changes to the store
- store.merge(builder, hook, CommitInfo.EMPTY);
-
- // Run async indexing explicitly
- for (int i = 0; i < 5; i++) {
- try (AsyncIndexUpdate async = new AsyncIndexUpdate("async", store,
provider)) {
- async.run();
- }
- }
- }
-}
-
diff --git a/oak-core/MergeTest.java b/oak-core/MergeTest.java
deleted file mode 100644
index c55d1c3ab6..0000000000
--- a/oak-core/MergeTest.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * 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.index.diff;
-
-import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.jackrabbit.oak.api.CommitFailedException;
-import org.apache.jackrabbit.oak.commons.json.JsonObject;
-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.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeStore;
-import org.junit.Test;
-
-public class MergeTest {
-
- // test that we can extract the file from the diff.json node (just that)
- @Test
- public void extractFile() {
- JsonObject indexDiff = JsonObject.fromJson("{\n"
- + " \"damAssetLucene\": {\n"
- + " \"indexRules\": {\n"
- + " \"dam:Asset\": {\n"
- + " \"properties\": {\n"
- + " \"y\": {\n"
- + " \"name\":
\"y\",\n"
- + "
\"propertyIndex\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }", true);
- String indexDiffString = indexDiff.toString();
- String base64Prop =
- "\":blobId:" +
Base64.getEncoder().encodeToString(indexDiffString.getBytes(StandardCharsets.UTF_8))
+ "\"";
- JsonObject repositoryDefinitions = JsonObject.fromJson("{\n"
- + " \"/oak:index/damAssetLucene-12\":
{\n"
- + " \"jcr:primaryType\":
\"oak:IndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"async\": [\"async\",
\"nrt\"],\n"
- + " \"tags\": [\"abc\"],\n"
- + " \"includedPaths\":
\"/content/dam\",\n"
- + " \"indexRules\": {\n"
- + " \"dam:Asset\": {\n"
- + " \"properties\": {\n"
- + " \"x\": {\n"
- + " \"name\":
\"x\",\n"
- + "
\"propertyIndex\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " },\n"
- + " \"/oak:index/diff.index\": {\n"
- + " \"jcr:primaryType\":
\"nt:unstructured\",\n"
- + " \"type\": \"lucene\",
\"includedPaths\": \"/same\", \"queryPaths\": \"/same\",\n"
- + " \"diff.json\": {\n"
- + " \"jcr:primaryType\":
\"nam:nt:file\",\n"
- + " \"jcr:content\": {\n"
- + " \"jcr:primaryType\":
\"nam:nt:resource\",\n"
- + " \"jcr:mimeType\":
\"application/json\",\n"
- + " \"jcr:data\":\n"
- + " " + base64Prop + "\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }", true);
-
- HashMap<String, JsonObject> target = new HashMap<>();
- DiffIndexMerger.tryExtractDiffIndex(repositoryDefinitions,
"/oak:index/diff.index", target);
- assertEquals("{damAssetLucene={\n"
- + " \"indexRules\": {\n"
- + " \"dam:Asset\": {\n"
- + " \"properties\": {\n"
- + " \"y\": {\n"
- + " \"name\": \"y\",\n"
- + " \"propertyIndex\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}}", target.toString());
- }
-
- @Test
- public void renamedProperty() {
- // A property might be indexed twice, by adding two children to the
"properties" node
- // that both have the same "name" value.
- // Alternatively, they could have the same "function" value.
- String merged =
DiffIndexMerger.instance().processMerge(JsonObject.fromJson("{\n"
- + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"acme:Test\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"abc\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"name\": \"test\",\n"
- + " \"boost\": 1.0\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }"
- + "", true), JsonObject.fromJson("{\n"
- + " \"indexRules\": {\n"
- + " \"acme:Test\": {\n"
- + " \"properties\": {\n"
- + " \"def\": {\n"
- + " \"name\": \"test\",\n"
- + " \"boost\": 1.2\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }", true)).toString();
- assertEquals("{\n"
- + " \"jcr:primaryType\": \"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"acme:Test\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"abc\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"name\": \"test\",\n"
- + " \"boost\": 1.2\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", merged);
- }
-
- @Test
- public void renamedFunction() {
- // A function might be indexed twice, by adding two children to the
"properties" node
- // that both have the same "function" value.
- String merged =
DiffIndexMerger.instance().processMerge(JsonObject.fromJson("{\n"
- + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"acme:Test\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"abc\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"function\": \"upper(test)\",\n"
- + " \"boost\": 1.0\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }"
- + "", true), JsonObject.fromJson("{\n"
- + " \"indexRules\": {\n"
- + " \"acme:Test\": {\n"
- + " \"properties\": {\n"
- + " \"def\": {\n"
- + " \"function\": \"upper(test)\",\n"
- + " \"boost\": 1.2\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }", true)).toString();
- assertEquals("{\n"
- + " \"jcr:primaryType\": \"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"acme:Test\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"abc\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"function\": \"upper(test)\",\n"
- + " \"boost\": 1.2\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", merged);
- }
-
- @Test
- public void createDummy() {
- // when enabling "deleteCreatesDummyIndex", then a dummy index is
created
- // (that indexes /dummy, which doesn't exist)
- String merged = new DiffIndexMerger(new String[0], true, true,
false).processMerge(JsonObject.fromJson("{}"
- + "", true), JsonObject.fromJson("{}", true)).toString();
- assertEquals("{\n"
- + " \"async\": \"async\",\n"
- + " \"includedPaths\": \"/dummy\",\n"
- + " \"queryPaths\": \"/dummy\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"jcr:primaryType\": \"nam:oak:QueryIndexDefinition\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"dummy\": {\n"
- + " \"name\": \"dummy\",\n"
- + " \"propertyIndex\": true,\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", merged);
- }
-
- @Test
- public void boost() {
- // - "analyzed" must not be overwritten
- // - "ordered" is added
- // - "boost" is overwritten
- String merged =
DiffIndexMerger.instance().processMerge(JsonObject.fromJson("{\n"
- + " \"jcr:primaryType\":
\"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"acme:Test\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"abc\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"analyzed\": true,\n"
- + " \"boost\": 1.0\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }"
- + "", true), JsonObject.fromJson("{\n"
- + " \"indexRules\": {\n"
- + " \"acme:Test\": {\n"
- + " \"properties\": {\n"
- + " \"abc\": {\n"
- + " \"analyzed\": false,\n"
- + " \"ordered\": true,\n"
- + " \"boost\": 1.2\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }", true)).toString();
- assertEquals("{\n"
- + " \"jcr:primaryType\": \"nam:oak:QueryIndexDefinition\",\n"
- + " \"type\": \"lucene\",\n"
- + " \"indexRules\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"acme:Test\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"properties\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"abc\": {\n"
- + " \"jcr:primaryType\": \"nam:nt:unstructured\",\n"
- + " \"analyzed\": true,\n"
- + " \"boost\": 1.2,\n"
- + " \"ordered\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", merged);
- }
-
- @Test
- public void mergeDiffsTest() {
- JsonObject a = JsonObject.fromJson("{\n"
- + " \"indexRules\": {\n"
- + " \"acme:Test\": {\n"
- + " \"properties\": {\n"
- + " \"prop1\": {\n"
- + " \"name\": \"field1\",\n"
- + " \"propertyIndex\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " },\n"
- + " \"type\": \"lucene\"\n"
- + " }", true);
- JsonObject b = JsonObject.fromJson("{\n"
- + " \"indexRules\": {\n"
- + " \"acme:Test\": {\n"
- + " \"properties\": {\n"
- + " \"prop2\": {\n"
- + " \"name\": \"field2\",\n"
- + " \"ordered\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " },\n"
- + " \"async\": [\"async\", \"nrt\"]\n"
- + " }", true);
- String merged = DiffIndexMerger.mergeDiffs(a, b).toString();
- assertEquals("{\n"
- + " \"type\": \"lucene\",\n"
- + " \"async\": [\"async\", \"nrt\"],\n"
- + " \"indexRules\": {\n"
- + " \"acme:Test\": {\n"
- + " \"properties\": {\n"
- + " \"prop1\": {\n"
- + " \"name\": \"field1\",\n"
- + " \"propertyIndex\": true\n"
- + " },\n"
- + " \"prop2\": {\n"
- + " \"name\": \"field2\",\n"
- + " \"ordered\": true\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", merged);
- }
-
- @Test
- public void switchToLuceneChildrenTest() {
- JsonObject indexDef = JsonObject.fromJson("{\n"
- + " \"type\": \"elasticsearch\",\n"
- + " \"type@lucene\": \"lucene\",\n"
- + " \"async@lucene\": \"[\\\"async\\\", \\\"nrt\\\"]\",\n"
- + " \"async\": \"[\\\"async\\\"]\",\n"
- + " \"codec@lucene\": \"Lucene46\",\n"
- + " \"indexRules\": {\n"
- + " \"dam:Asset\": {\n"
- + " \"properties\": {\n"
- + " \"test\": {\n"
- + " \"name\": \"jcr:content/metadata/test\",\n"
- + " \"boost@lucene\": \"2.0\",\n"
- + " \"boost\": \"1.0\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }", true);
- DiffIndexMerger.switchToLuceneChildren(indexDef);
- String result = indexDef.toString();
- assertEquals("{\n"
- + " \"type\": \"lucene\",\n"
- + " \"async\": \"[\\\"async\\\", \\\"nrt\\\"]\",\n"
- + " \"codec\": \"Lucene46\",\n"
- + " \"indexRules\": {\n"
- + " \"dam:Asset\": {\n"
- + " \"properties\": {\n"
- + " \"test\": {\n"
- + " \"name\": \"jcr:content/metadata/test\",\n"
- + " \"boost\": \"2.0\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}", result);
- }
-
- @Test
- public void includesUnsupportedPathsTest() {
- DiffIndexMerger merger = new DiffIndexMerger(new String[]{"/apps",
"/libs"}, false, false, false);
-
- assertEquals(true, merger.includesUnsupportedPaths(null));
- assertEquals(true, merger.includesUnsupportedPaths(new String[]{"/"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/apps"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/apps/acme"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/apps/acme/test"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/libs"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/libs/foundation"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/content", "/apps"}));
- assertEquals(true, merger.includesUnsupportedPaths(new
String[]{"/content", "/libs/test"}));
-
- assertEquals(false, merger.includesUnsupportedPaths(new
String[]{"/content"}));
- assertEquals(false, merger.includesUnsupportedPaths(new
String[]{"/content/dam"}));
- assertEquals(false, merger.includesUnsupportedPaths(new
String[]{"/var"}));
- assertEquals(false, merger.includesUnsupportedPaths(new
String[]{"/etc"}));
- assertEquals(false, merger.includesUnsupportedPaths(new
String[]{"/content", "/var", "/etc"}));
- }
-
- @Test
- public void readDiffIndexTest() throws CommitFailedException {
- NodeStore store = new MemoryNodeStore(INITIAL_CONTENT);
- NodeBuilder root = store.getRoot().builder();
- NodeBuilder oakIndex = root.child("oak:index");
- NodeBuilder diffIndex = oakIndex.child("diff.index.optimizer");
- diffIndex.setProperty("jcr:primaryType", "nt:unstructured");
- diffIndex.setProperty("type", "lucene");
- diffIndex.setProperty("async", "async");
- diffIndex.setProperty("includedPaths", "/content");
- NodeBuilder indexRules = diffIndex.child("indexRules");
- NodeBuilder damAsset = indexRules.child("dam:Asset");
- NodeBuilder properties = damAsset.child("properties");
- NodeBuilder testProp = properties.child("test");
- testProp.setProperty("name", "jcr:content/metadata/test");
- testProp.setProperty("propertyIndex", true);
- store.merge(root, EmptyHook.INSTANCE, CommitInfo.EMPTY);
-
- Map<String, JsonObject> result =
DiffIndexMerger.instance().readDiffIndex(store, "diff.index.optimizer");
-
- assertEquals(1, result.size());
- assertTrue(result.containsKey("/oak:index/diff.index.optimizer"));
- JsonObject indexDef = result.get("/oak:index/diff.index.optimizer");
- assertEquals("\"lucene\"", indexDef.getProperties().get("type"));
- assertEquals("\"async\"", indexDef.getProperties().get("async"));
- assertEquals("\"/content\"",
indexDef.getProperties().get("includedPaths"));
- assertTrue(indexDef.getChildren().containsKey("indexRules"));
- }
-}