This is an automated email from the ASF dual-hosted git repository.

thomasm pushed a commit to branch OAK-12010-subset
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/OAK-12010-subset by this push:
     new e26f834ff8 Remove misplaced files
e26f834ff8 is described below

commit e26f834ff800a6a9274635e3a2dda3f5bc499375
Author: Thomas Mueller <[email protected]>
AuthorDate: Tue Feb 10 16:01:22 2026 +0100

    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"));
-    }
-}


Reply via email to