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

nfsantos pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new fc066c3d68 OAK-11658 - Nodes of type nt:resource in new index 
definitions must contain a jcr:uuid and it must be unique (#2231)
fc066c3d68 is described below

commit fc066c3d6815c373bcc348d89dcc7465a6276ac6
Author: Nuno Santos <[email protected]>
AuthorDate: Fri Apr 18 09:39:44 2025 +0200

    OAK-11658 - Nodes of type nt:resource in new index definitions must contain 
a jcr:uuid and it must be unique (#2231)
---
 .../index/importer/IndexDefinitionUpdater.java     | 87 +++++++++++++++-----
 .../index/importer/IndexDefinitionUpdaterTest.java | 96 ++++++++++++++++------
 .../index/importer/index-def-jcruuid-fix.json      | 24 ++++++
 3 files changed, 165 insertions(+), 42 deletions(-)

diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdater.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdater.java
index 08571d448b..18944e0ac9 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdater.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdater.java
@@ -18,16 +18,12 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.importer;
 
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.commons.conditions.Validate;
 import org.apache.jackrabbit.oak.commons.json.JsopReader;
@@ -43,6 +39,15 @@ import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
 import static 
org.apache.jackrabbit.oak.commons.conditions.Validate.checkArgument;
 import static 
org.apache.jackrabbit.oak.plugins.index.importer.NodeStoreUtils.childBuilder;
 import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
@@ -52,7 +57,8 @@ public class IndexDefinitionUpdater {
      * Name of file which would be check for presence of index-definitions
      */
     public static final String INDEX_DEFINITIONS_JSON = 
"index-definitions.json";
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    private static final Logger LOG = 
LoggerFactory.getLogger(IndexDefinitionUpdater.class);
+
     private final Map<String, NodeState> indexNodeStates;
 
     public IndexDefinitionUpdater(File file) throws IOException {
@@ -75,25 +81,24 @@ public class IndexDefinitionUpdater {
         String indexNodeName = PathUtils.getName(indexPath);
 
         NodeState newDefinition = indexNodeStates.get(indexPath);
+        newDefinition = addOrModifyJcrUUID(newDefinition);
         String parentPath = PathUtils.getParentPath(indexPath);
         NodeState parent = NodeStateUtils.getNode(rootBuilder.getBaseState(), 
parentPath);
 
         Validate.checkState(parent.exists(), "Parent node at path [%s] not 
found while " +
                 "adding new index definition for [%s]. Intermediate paths node 
must exist for new index " +
-                "nodes to be created", parentPath,indexPath);
+                "nodes to be created", parentPath, indexPath);
 
         NodeState indexDefinitionNode = parent.getChildNode(indexNodeName);
 
         if (indexDefinitionNode.exists()) {
-            log.info("Updating index definition at path [{}]. Changes are ", 
indexPath);
+            LOG.info("Updating index definition at path [{}]. Changes are ", 
indexPath);
             String diff = 
JsopDiff.diffToJsop(cloneVisibleState(indexDefinitionNode), 
cloneVisibleState(newDefinition));
-            log.info(diff);
+            LOG.info(diff);
         } else {
-            log.info("Adding new index definition at path [{}]", indexPath);
+            LOG.info("Adding new index definition at path [{}]", indexPath);
         }
 
-
-
         NodeBuilder indexBuilderParent = childBuilder(rootBuilder, parentPath);
 
         //Use Tree api to ensure that :childOrder property is properly updated
@@ -104,15 +109,15 @@ public class IndexDefinitionUpdater {
         return indexBuilderParent.getChildNode(indexNodeName);
     }
 
-    public Set<String> getIndexPaths(){
+    public Set<String> getIndexPaths() {
         return indexNodeStates.keySet();
     }
 
-    public NodeState getIndexState(String indexPath){
+    public NodeState getIndexState(String indexPath) {
         return indexNodeStates.getOrDefault(indexPath, EMPTY_NODE);
     }
 
-    private static Map<String, NodeState> getIndexDefnStates(String json) 
throws IOException {
+    private static Map<String, NodeState> getIndexDefnStates(String json) {
         Base64BlobSerializer blobHandler = new Base64BlobSerializer();
         Map<String, NodeState> indexDefns = new HashMap<>();
         JsopReader reader = new JsopTokenizer(json);
@@ -139,12 +144,18 @@ public class IndexDefinitionUpdater {
         return indexDefns;
     }
 
-    private static NodeState cloneVisibleState(NodeState state){
+    private static NodeState cloneVisibleState(NodeState state) {
         NodeBuilder builder = EMPTY_NODE.builder();
         new ApplyVisibleDiff(builder).apply(state);
         return builder.getNodeState();
     }
 
+    private static NodeState addOrModifyJcrUUID(NodeState state) {
+        NodeBuilder builder = EMPTY_NODE.builder();
+        new AddOrUpdateJCRUuid(builder).apply(state);
+        return builder.getNodeState();
+    }
+
     private static class ApplyVisibleDiff extends ApplyDiff {
         public ApplyVisibleDiff(NodeBuilder builder) {
             super(builder);
@@ -152,11 +163,49 @@ public class IndexDefinitionUpdater {
 
         @Override
         public boolean childNodeAdded(String name, NodeState after) {
-            if (NodeStateUtils.isHidden(name)){
+            if (NodeStateUtils.isHidden(name)) {
                 return true;
             }
             return after.compareAgainstBaseState(
                     EMPTY_NODE, new ApplyVisibleDiff(builder.child(name)));
         }
     }
+
+    /**
+     * Change the new index definitions to comply with Oak requirements that 
all nt:resource nodes
+     * must have a jcr:uuid property. Additionally, if the definition contains 
nt:resource nodes
+     * with a jcr:uuid, replace them by new UUIDs to avoid any possible 
conflicts with the existing
+     * definitions. This is for cases where the new definition is copied from 
an existing one and
+     * the UUIDs are not changed.
+     */
+    private static class AddOrUpdateJCRUuid extends ApplyDiff {
+        public AddOrUpdateJCRUuid(NodeBuilder builder) {
+            super(builder);
+        }
+
+        @Override
+        public boolean propertyAdded(PropertyState after) {
+            if (after.getName().equals(JcrConstants.JCR_UUID)) {
+                addNewJcrUUIDProperty(builder);
+            } else {
+                builder.setProperty(after);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean childNodeAdded(String name, NodeState after) {
+            NodeBuilder newChild = builder.setChildNode(name, after);
+            String primaryType = after.getName(JcrConstants.JCR_PRIMARYTYPE);
+            if (primaryType != null && 
primaryType.equals(JcrConstants.NT_RESOURCE) && 
!newChild.hasProperty(JcrConstants.JCR_UUID)) {
+                addNewJcrUUIDProperty(newChild);
+            }
+            return after.compareAgainstBaseState(EMPTY_NODE, new 
AddOrUpdateJCRUuid(builder.child(name)));
+        }
+
+        private static void addNewJcrUUIDProperty(NodeBuilder builder) {
+            String uuid = 
UUID.randomUUID().toString().toLowerCase(Locale.ROOT);
+            builder.setProperty(JcrConstants.JCR_UUID, uuid, Type.STRING);
+        }
+    }
 }
diff --git 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdaterTest.java
 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdaterTest.java
index 78d00fa96e..09be2490de 100644
--- 
a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdaterTest.java
+++ 
b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/importer/IndexDefinitionUpdaterTest.java
@@ -18,15 +18,9 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.importer;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
+import org.apache.commons.io.IOUtils;
 import org.apache.felix.inventory.Format;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Tree;
@@ -42,24 +36,39 @@ import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 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.jetbrains.annotations.Nullable;
 import org.json.simple.JSONObject;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
 import static java.util.Arrays.asList;
 import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 public class IndexDefinitionUpdaterTest {
 
     @Rule
     public TemporaryFolder folder = new TemporaryFolder(new File("target"));
 
-    private NodeStore store = new MemoryNodeStore();
+    private final NodeStore store = new MemoryNodeStore();
 
     @Test
-    public void update() throws Exception{
+    public void update() throws Exception {
         NodeBuilder builder = store.getRoot().builder();
         builder.child("oak:index").child("fooIndex").setProperty("foo", "bar");
         
builder.child("oak:index").child("fooIndex").child("a").setProperty("foo2", 2);
@@ -71,15 +80,15 @@ public class IndexDefinitionUpdaterTest {
 
         NodeState root = store.getRoot();
 
-        
assertTrue(NodeStateUtils.getNode(root,"/oak:index/fooIndex").exists());
-        
assertTrue(NodeStateUtils.getNode(root,"/oak:index/fooIndex/b").exists());
-        
assertFalse(NodeStateUtils.getNode(root,"/oak:index/fooIndex/a").exists());
+        assertTrue(NodeStateUtils.getNode(root, 
"/oak:index/fooIndex").exists());
+        assertTrue(NodeStateUtils.getNode(root, 
"/oak:index/fooIndex/b").exists());
+        assertFalse(NodeStateUtils.getNode(root, 
"/oak:index/fooIndex/a").exists());
 
-        
assertTrue(NodeStateUtils.getNode(root,"/oak:index/barIndex").exists());
+        assertTrue(NodeStateUtils.getNode(root, 
"/oak:index/barIndex").exists());
     }
 
-    @Test (expected = IllegalStateException.class)
-    public void updateNonExistingParent() throws Exception{
+    @Test(expected = IllegalStateException.class)
+    public void updateNonExistingParent() throws Exception {
         NodeBuilder builder = store.getRoot().builder();
         builder.child("oak:index").child("fooIndex").setProperty("foo", "bar");
 
@@ -91,8 +100,8 @@ public class IndexDefinitionUpdaterTest {
         applyJson(json);
     }
 
-    @Test (expected = IllegalArgumentException.class)
-    public void invalidJson() throws Exception{
+    @Test(expected = IllegalArgumentException.class)
+    public void invalidJson() throws Exception {
         Map<String, Object> map = new HashMap<>();
         map.put("a", Map.of("a2", "b2"));
         String json = JSONObject.toJSONString(map);
@@ -100,7 +109,7 @@ public class IndexDefinitionUpdaterTest {
     }
 
     @Test
-    public void applyToIndexPath() throws Exception{
+    public void applyToIndexPath() throws Exception {
         String json = "{\"/oak:index/barIndex\": {\n" +
                 "    \"compatVersion\": 2,\n" +
                 "    \"type\": \"lucene\",\n" +
@@ -122,7 +131,7 @@ public class IndexDefinitionUpdaterTest {
     }
 
     @Test
-    public void newIndexAndOrderableChildren() throws Exception{
+    public void newIndexAndOrderableChildren() throws Exception {
         String json = "{\"/oak:index/barIndex\": {\n" +
                 "    \"compatVersion\": 2,\n" +
                 "    \"type\": \"lucene\",\n" +
@@ -149,6 +158,48 @@ public class IndexDefinitionUpdaterTest {
 
     }
 
+
+    @Test
+    public void fixJCRUUID() throws Exception {
+        String json;
+        try (InputStream is = 
getClass().getResourceAsStream("/org/apache/jackrabbit/oak/plugins/index/importer/index-def-jcruuid-fix.json"))
 {
+            json = IOUtils.toString(is, StandardCharsets.UTF_8);
+        }
+
+        NodeBuilder builder = store.getRoot().builder();
+        Tree root = TreeFactory.createTree(builder);
+        Tree oakIndex = root.addChild("oak:index");
+        store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        IndexDefinitionUpdater updater = new IndexDefinitionUpdater(json);
+        builder = store.getRoot().builder();
+
+        NodeBuilder idxBuilder = updater.apply(builder, "/oak:index/barIndex");
+
+        NodeBuilder barIndexBuilder = builder
+                .getChildNode("oak:index")
+                .getChildNode("barIndex");
+        // The node config_without_uuid.xml does not contain any jcr:uuid, so 
we only check that now there is one
+        checkContainsJcrUUID(barIndexBuilder
+                .getChildNode("config_without_uuid.xml")
+                .getChildNode("jcr:content"));
+
+        // This node config_with_uuid.xml contains a jcr:uuid, so check that 
the new jcr:uuid is different
+        UUID newUuid = checkContainsJcrUUID(barIndexBuilder
+                .getChildNode("config_with_uuid.xml")
+                .getChildNode("jcr:content"));
+        
assertFalse(newUuid.toString().equalsIgnoreCase("6db23ff7-986d-4c3d-bf3d-ea9eaf8b5f65"));
+
+    }
+
+    private UUID checkContainsJcrUUID(NodeBuilder builder) {
+        @Nullable PropertyState jcrUuid = 
builder.getProperty(JcrConstants.JCR_UUID);
+        assertNotNull(jcrUuid);
+        String newUuid = jcrUuid.getValue(Type.STRING);
+        assertNotNull(newUuid);
+        return UUID.fromString(newUuid);
+    }
+
     private void applyJson(String json) throws IOException, 
CommitFailedException {
         IndexDefinitionUpdater update = new IndexDefinitionUpdater(json);
 
@@ -177,8 +228,7 @@ public class IndexDefinitionUpdaterTest {
 
         store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
 
-        IndexDefinitionPrinter printer = new IndexDefinitionPrinter(store,
-                () -> asList(indexPaths));
+        IndexDefinitionPrinter printer = new IndexDefinitionPrinter(store, () 
-> asList(indexPaths));
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
diff --git 
a/oak-core/src/test/resources/org/apache/jackrabbit/oak/plugins/index/importer/index-def-jcruuid-fix.json
 
b/oak-core/src/test/resources/org/apache/jackrabbit/oak/plugins/index/importer/index-def-jcruuid-fix.json
new file mode 100644
index 0000000000..a403147507
--- /dev/null
+++ 
b/oak-core/src/test/resources/org/apache/jackrabbit/oak/plugins/index/importer/index-def-jcruuid-fix.json
@@ -0,0 +1,24 @@
+{
+  "/oak:index/barIndex": {
+    "jcr:primaryType": "nam:oak:QueryIndexDefinition",
+    "config_without_uuid.xml": {
+      "jcr:created": "dat:2024-07-11T11:37:51.727Z",
+      "jcr:primaryType": "nam:nt:file",
+      "jcr:content": {
+        "jcr:data": ":blobId:XYZ",
+        "jcr:mimeType": "text/xml",
+        "jcr:primaryType": "nam:nt:resource"
+      }
+    },
+    "config_with_uuid.xml": {
+      "jcr:created": "dat:2024-07-11T11:37:51.727Z",
+      "jcr:primaryType": "nam:nt:file",
+      "jcr:content": {
+        "jcr:data": ":blobId:XYZ",
+        "jcr:mimeType": "text/xml",
+        "jcr:primaryType": "nam:nt:resource",
+        "jcr:uuid": "6db23ff7-986d-4c3d-bf3d-ea9eaf8b5f65"
+      }
+    }
+  }
+}

Reply via email to