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

kwin pushed a commit to branch bugfix/fallback-for-primary-type-retrieval
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-resource.git

commit 45a0c9fe53c01290e576915792f12f7466762816
Author: Konrad Windszus <[email protected]>
AuthorDate: Mon Sep 22 21:50:09 2025 +0200

    SLING-12944 Retrieve primary type as fallback via
    Node.getPrimaryNodeType()
    
    The permission evaluation is skipped for this call.
    Also make sure to populate the cache with entries for both
    "sling:resourceType" and "jcr:primaryType" with readFully()
---
 pom.xml                                            |  2 +-
 .../sling/jcr/resource/internal/JcrValueMap.java   | 55 ++++++++++++++++++++--
 .../jcr/resource/internal/JcrValueMapTest.java     | 27 ++++++++++-
 .../internal/helper/jcr/JcrNodeResourceTest.java   |  2 +
 4 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index 05772f6..a6c2066 100644
--- a/pom.xml
+++ b/pom.xml
@@ -213,7 +213,7 @@
         <dependency>
             <groupId>org.hamcrest</groupId>
             <artifactId>hamcrest</artifactId>
-            <version>2.2</version>
+            <version>3.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git 
a/src/main/java/org/apache/sling/jcr/resource/internal/JcrValueMap.java 
b/src/main/java/org/apache/sling/jcr/resource/internal/JcrValueMap.java
index 7384046..d549d2c 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/JcrValueMap.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/JcrValueMap.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import java.io.IOException;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
@@ -277,6 +278,35 @@ public class JcrValueMap implements ValueMap {
         }
     }
 
+    private @NotNull JcrPropertyMapCacheEntry cacheProperty(final @NotNull 
String key, final @NotNull Object value, final @NotNull Node node) {
+        try {
+            JcrPropertyMapCacheEntry entry = cache.get(key);
+            if (entry == null) {
+                entry = new JcrPropertyMapCacheEntry(value, node);
+                cache.put(key, entry);
+                valueCache.put(key, entry.getPropertyValue());
+            }
+            return entry;
+        } catch (final RepositoryException|IOException re) {
+            throw new IllegalArgumentException(re);
+        }
+    }
+
+    /**
+     * Reads the primary type of the current node via {@link 
Node#getPrimaryNodeType()} and caches it.
+     * That way regular permission evaluation is bypassed (see <a 
href="https://issues.apache.org/jira/browse/OAK-2441";>OAK-2441</a>).
+     * Should only be used as fallback if regular access via {@link 
Node#getProperty(String)} fails as 
+     * calculating the NodeType is expensive.
+     * @return the cache entry for the primary type
+     */
+    private JcrPropertyMapCacheEntry readPrimaryType() {
+        try {
+            String primaryType = node.getPrimaryNodeType().getName();
+            return cacheProperty(JcrConstants.JCR_PRIMARYTYPE, primaryType, 
node);
+        } catch (final RepositoryException re) {
+            throw new IllegalArgumentException(re);
+        }
+    }
     /**
      * Read a single property.
      * @throws IllegalArgumentException if a repository exception occurs
@@ -300,10 +330,17 @@ public class JcrValueMap implements ValueMap {
         try {
             final String key = escapeKeyName(name);
             Property property = NodeUtil.getPropertyOrNull(node,key);
-            if (property == null && 
name.equals(ResourceResolver.PROPERTY_RESOURCE_TYPE)) {
-                // special handling for the resource type property which 
according to the API must always be exposed via property sling:resourceType
-                // use value of jcr:primaryType if sling:resourceType is not 
set
-                property = NodeUtil.getPropertyOrNull(node, 
JcrConstants.JCR_PRIMARYTYPE);
+            if (property == null) { 
+                if (name.equals(ResourceResolver.PROPERTY_RESOURCE_TYPE)) {
+                    // special handling for the resource type property which 
according to the API must always be exposed via property sling:resourceType
+                    // use value of jcr:primaryType if sling:resourceType is 
not set
+                    JcrPropertyMapCacheEntry entry = 
read(JcrConstants.JCR_PRIMARYTYPE);
+                    if (entry != null) {
+                        return cacheProperty(name, entry.getPropertyValue(), 
node);
+                    }
+                } else if (name.equals(JcrConstants.JCR_PRIMARYTYPE)) {
+                    return readPrimaryType();
+                }
             }
             if (property != null) {
                 return cacheProperty(property);
@@ -392,6 +429,16 @@ public class JcrValueMap implements ValueMap {
                     final Property prop = pi.nextProperty();
                     this.cacheProperty(prop);
                 }
+                // make sure primary type is in cache
+                if (!this.cache.containsKey(JcrConstants.JCR_PRIMARYTYPE)) {
+                    readPrimaryType();
+                }
+                // make sure sling:resourceType is in cache
+                if 
(!this.cache.containsKey(ResourceResolver.PROPERTY_RESOURCE_TYPE)) {
+                    // special handling for the resource type property which 
according to the API must always be exposed via property sling:resourceType
+                    // use value of jcr:primaryType if sling:resourceType is 
not set
+                    
this.cacheProperty(ResourceResolver.PROPERTY_RESOURCE_TYPE, 
valueCache.get(JcrConstants.JCR_PRIMARYTYPE), node);
+                }
                 fullyRead = true;
             } catch (final RepositoryException re) {
                 throw new IllegalArgumentException(re);
diff --git 
a/src/test/java/org/apache/sling/jcr/resource/internal/JcrValueMapTest.java 
b/src/test/java/org/apache/sling/jcr/resource/internal/JcrValueMapTest.java
index 07d5f57..7e334b7 100644
--- a/src/test/java/org/apache/sling/jcr/resource/internal/JcrValueMapTest.java
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/JcrValueMapTest.java
@@ -18,9 +18,16 @@
  */
 package org.apache.sling.jcr.resource.internal;
 
+import static org.mockito.Mockito.times;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.AbstractMap.SimpleEntry;
+
 import javax.jcr.Node;
+import javax.jcr.RepositoryException;
 
 import 
org.apache.sling.jcr.resource.internal.helper.jcr.SlingRepositoryTestBase;
+import org.hamcrest.Matchers;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -58,6 +65,24 @@ public class JcrValueMapTest extends SlingRepositoryTestBase 
{
         assertEquals("test", vm.get("string",null)); 
     }
     
-   
+    /**
+     * Make sure cache is fully populated even
+     * @throws RepositoryException 
+     */
+    @Test
+    public void testGetEntrySet() throws RepositoryException {
+        Node node = Mockito.spy(rootNode);
+        JcrValueMap vm = new JcrValueMap(node, helperData);
+        assertThat( vm.entrySet(), Matchers.hasSize(3) );
+        assertThat( vm.keySet(), Matchers.hasSize(3) );
+        assertThat( vm.values(), Matchers.hasSize(3) );
+        assertThat( vm.entrySet(), Matchers.containsInAnyOrder(
+                new SimpleEntry<String, Object>("jcr:primaryType", 
"nt:unstructured"),
+                new SimpleEntry<String, Object>("sling:resourceType", 
"nt:unstructured"),
+                new SimpleEntry<String, Object>("string", "test")
+                ));
+        // cache should only be populated once
+        Mockito.verify(node, times(1)).getProperties();
+    }
 
 }
diff --git 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java
 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java
index 3fa404e..73de47a 100644
--- 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java
+++ 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrNodeResourceTest.java
@@ -34,6 +34,7 @@ import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.jcr.resource.api.JcrResourceConstants;
 
@@ -220,6 +221,7 @@ public class JcrNodeResourceTest extends 
JcrItemResourceTestBase {
         existingKeys.add(JcrConstants.JCR_ENCODING);
         existingKeys.add(JcrConstants.JCR_DATA);
         existingKeys.add(JcrConstants.JCR_PRIMARYTYPE);
+        existingKeys.add(ResourceResolver.PROPERTY_RESOURCE_TYPE);
         final Set<Object> crossCheck = new HashSet<>(props.keySet());
         crossCheck.removeAll(existingKeys);
         assertTrue(crossCheck.isEmpty());

Reply via email to