From 0837869982d5802f240231c5efa4d9e3300d3e7a Mon Sep 17 00:00:00 2001 From: chibulcuteanu <24243104+chibulcuteanu@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:36:50 +0300 Subject: [PATCH] OAK-12352: Include diff indexes in the index definition status printer /oak:index/diff.index and /oak:index/diff.index.optimizer are not oak:QueryIndexDefinition nodes, so IndexPathService does not return them and they were missing from the oak-index-defn status printer output. Append them explicitly, inlining their diff.json payload as JSON instead of a base64 blob. All other properties and child nodes render as before. --- .../inventory/IndexDefinitionPrinter.java | 56 +++++++++++++++++++ .../inventory/IndexDefinitionPrinterTest.java | 32 +++++++++++ 2 files changed, 88 insertions(+) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinter.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinter.java index 55bfd182517..4d400d547ff 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinter.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinter.java @@ -23,10 +23,15 @@ import org.apache.felix.inventory.Format; import org.apache.felix.inventory.InventoryPrinter; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.json.JsonObject; import org.apache.jackrabbit.oak.commons.json.JsopBuilder; import org.apache.jackrabbit.oak.json.Base64BlobSerializer; import org.apache.jackrabbit.oak.json.JsonSerializer; import org.apache.jackrabbit.oak.plugins.index.IndexPathService; +import org.apache.jackrabbit.oak.plugins.index.diff.DiffIndex; +import org.apache.jackrabbit.oak.plugins.index.diff.DiffIndexMerger; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; import org.apache.jackrabbit.oak.spi.state.NodeStore; @@ -69,6 +74,17 @@ public void print(PrintWriter printWriter, Format format, boolean isZip) { NodeState idxState = NodeStateUtils.getNode(root, indexPath); createSerializer(json).serialize(idxState); } + // The "diff" indexes (diff.index / diff.index.optimizer) are not oak:QueryIndexDefinition nodes, so they + // are not returned by the IndexPathService and would otherwise be missing from the output. Add them + // explicitly, rendering their diff.json payload as inline JSON so the pending diff is readable. + for (String name : new String[] {DiffIndexMerger.DIFF_INDEX, DiffIndexMerger.DIFF_INDEX_OPTIMIZER}) { + String diffPath = "/oak:index/" + name; + NodeState idxState = NodeStateUtils.getNode(root, diffPath); + if (idxState.exists()) { + json.key(diffPath); + serializeDiffIndex(json, idxState); + } + } json.endObject(); printWriter.print(JsopBuilder.prettyPrint(json.toString())); } @@ -81,4 +97,44 @@ public void setFilter(String filter) { private JsonSerializer createSerializer(JsopBuilder json) { return new JsonSerializer(json, filter, new Base64BlobSerializer()); } + + /** + * Serialize a diff index node, inlining its {@code diff.json} payload as JSON. All other file child nodes are + * rendered as base64 blobs, for backward compatibility. + */ + private void serializeDiffIndex(JsopBuilder json, NodeState idxState) { + json.object(); + JsonSerializer serializer = createSerializer(json); + // definition properties (mirror the default filter, which drops :childOrder) + for (PropertyState p : idxState.getProperties()) { + if (":childOrder".equals(p.getName())) { + continue; + } + json.key(p.getName()); + serializer.serialize(p); + } + // non-hidden child nodes other than diff.json, rendered normally + for (ChildNodeEntry child : idxState.getChildNodeEntries()) { + String childName = child.getName(); + if (childName.startsWith(":") || "diff.json".equals(childName)) { + continue; + } + json.key(childName); + createSerializer(json).serialize(child.getNodeState()); + } + // diff.json: inline the JSON payload instead of a base64 blob + NodeState content = idxState.getChildNode("diff.json").getChildNode("jcr:content"); + String diff = content.exists() ? DiffIndex.tryReadString(content.getProperty("jcr:data")) : null; + if (diff != null) { + json.key("diff.json"); + try { + // Parse and re-serialize so a malformed diff.json cannot corrupt the whole output. + JsonObject.fromJson(diff, true).toJson(json); + } catch (Exception e) { + // Not valid JSON - keep the endpoint well-formed by emitting the raw payload as a string. + json.value(diff); + } + } + json.endObject(); + } } diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinterTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinterTest.java index 453c891bb0b..cb884e21e04 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinterTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/inventory/IndexDefinitionPrinterTest.java @@ -20,6 +20,7 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.felix.inventory.Format; @@ -35,6 +36,7 @@ import org.json.simple.JSONValue; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -88,6 +90,36 @@ public void binaryProps() throws Exception{ assertTrue(updater.getIndexPaths().contains("/a")); } + @Test + public void diffIndexInlined() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + NodeBuilder diffIndex = builder.child("oak:index").child("diff.index"); + diffIndex.setProperty("type", "lucene"); + diffIndex.setProperty("async", "async"); + String payload = "{\"damAssetLucene\":{\"indexRules\":{\"dam:Asset\":" + + "{\"properties\":{\"foo\":{\"propertyIndex\":true}}}}}}"; + diffIndex.child("diff.json").child("jcr:content") + .setProperty("jcr:data", new ArrayBasedBlob(payload.getBytes(StandardCharsets.UTF_8))); + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // diff.index is not returned by the path service, yet it must still appear in the output + when(pathService.getIndexPaths()).thenReturn(List.of()); + + String json = getJSON(); + JSONObject o = (JSONObject) JSONValue.parseWithException(json); + + JSONObject diff = (JSONObject) o.get("/oak:index/diff.index"); + assertNotNull("diff.index should be present in the output", diff); + assertEquals("lucene", diff.get("type")); + + // diff.json must be inlined as a JSON object, not a base64 blob string + Object diffJson = diff.get("diff.json"); + assertTrue("diff.json should be inlined as a JSON object", diffJson instanceof JSONObject); + JSONObject damAsset = (JSONObject) ((JSONObject) diffJson).get("damAssetLucene"); + assertNotNull(damAsset); + assertNotNull(damAsset.get("indexRules")); + } + private String getJSON() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw);