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

radu pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-resource.git


The following commit(s) were added to refs/heads/master by this push:
     new 9548733  SLING-12300 - Provide a way to retrieve a JCR backed resource 
by its node identifier
9548733 is described below

commit 954873381c344f3558af1cd97fca9ae41d47ae63
Author: Radu Cotescu <[email protected]>
AuthorDate: Mon May 6 08:12:32 2024 -0400

    SLING-12300 - Provide a way to retrieve a JCR backed resource by its node 
identifier
    
    * added support for a /jcr:id/ prefix to the JcrItemResourceFactory when 
retrieving
    items by path
    * added a configuration flag for conditionally enabling resource id 
addressing
    * made sure to return the JCR path no matter which retrieval mode was 
performed
---
 .../helper/jcr/JcrItemResourceFactory.java         | 22 +++++---
 .../internal/helper/jcr/JcrResourceProvider.java   | 40 +++++++++++++--
 .../helper/jcr/JcrItemResourceFactoryTest.java     | 60 +++++++++++++++++++---
 .../JcrResourceProviderSessionHandlingTest.java    | 28 +++++-----
 .../helper/jcr/JcrResourceProviderTest.java        | 60 +++++++++++++++++++---
 5 files changed, 174 insertions(+), 36 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactory.java
 
b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactory.java
index f5f9a99..6bd3806 100644
--- 
a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactory.java
+++ 
b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactory.java
@@ -43,6 +43,8 @@ import org.slf4j.LoggerFactory;
 
 public class JcrItemResourceFactory {
 
+    static final String SEARCH_BY_ID_PREFIX = "/jcr:id/";
+
     /** Default logger */
     private static final Logger log = 
LoggerFactory.getLogger(JcrItemResourceFactory.class);
 
@@ -69,7 +71,7 @@ public class JcrItemResourceFactory {
      * @param parent The parent resource or {@code null}
      * @param parameters The parameters or{@code null}
      * @return The <code>Resource</code> for the item at the given path.
-     * @throws RepositoryException If an error occurrs accessing checking the
+     * @throws RepositoryException If an error occurs accessing checking the
      *             item in the repository.
      */
     public @Nullable JcrItemResource<?> createResource(final @NotNull 
ResourceResolver resourceResolver, final @NotNull String resourcePath,
@@ -82,15 +84,20 @@ public class JcrItemResourceFactory {
         }
         
         Item item = getItem(resourcePath, parent, version);
-        
+
         if (item == null) {
             log.debug("createResource: No JCR Item exists at path '{}'", 
resourcePath);
             return null;
         } else {
             final JcrItemResource<?> resource;
             if (item.isNode()) {
-                log.debug("createResource: Found JCR Node Resource at path 
'{}'", resourcePath);
-                resource = new JcrNodeResource(resourceResolver, resourcePath, 
version, (Node) item, helper);
+                if (JcrResourceProvider.isIdAddressingEnabled() && 
resourcePath.startsWith(SEARCH_BY_ID_PREFIX)) {
+                    log.debug("createResource: Found JCR Node Resource by ID 
at path '{}'", resourcePath);
+                    resource = new JcrNodeResource(resourceResolver, 
item.getPath(), version, (Node) item, helper);
+                } else {
+                    log.debug("createResource: Found JCR Node Resource at path 
'{}'", resourcePath);
+                    resource = new JcrNodeResource(resourceResolver, 
resourcePath, version, (Node) item, helper);
+                }
             } else {
                 log.debug("createResource: Found JCR Property Resource at path 
'{}'", resourcePath);
                 resource = new JcrPropertyResource(resourceResolver, 
resourcePath, version, (Property) item);
@@ -185,8 +192,11 @@ public class JcrItemResourceFactory {
 
         Item item = null;
         try {
-            // Use fast getItemOrNull if session is a JackrabbitSession
-            if (this.isJackrabbit) {
+            // check if the lookup is by ID
+            if (JcrResourceProvider.isIdAddressingEnabled() && 
path.startsWith(SEARCH_BY_ID_PREFIX)) {
+                item = 
session.getNodeByIdentifier(path.substring(SEARCH_BY_ID_PREFIX.length()));
+            } else if (this.isJackrabbit) {
+                // Use fast getItemOrNull if session is a JackrabbitSession
                 item = ((JackrabbitSession) session).getItemOrNull(path);
             }
             // Fallback to slower itemExists & getItem pattern
diff --git 
a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
 
b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
index 1022a7f..402d0a4 100644
--- 
a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
+++ 
b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
@@ -32,8 +32,6 @@ import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.NotNull;
 import javax.jcr.Item;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
@@ -66,6 +64,8 @@ import 
org.apache.sling.spi.resource.provider.QueryLanguageProvider;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.ComponentContext;
@@ -75,6 +75,9 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -95,6 +98,9 @@ import static 
org.apache.sling.jcr.resource.internal.helper.jcr.ContextUtil.getS
                    ResourceProvider.PROPERTY_AUTHENTICATE + "=" + 
ResourceProvider.AUTHENTICATE_REQUIRED,
                    Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
            })
+@Designate(
+        ocd = JcrResourceProvider.Configuration.class
+)
 public class JcrResourceProvider extends ResourceProvider<JcrProviderState> {
 
     // due to https://issues.apache.org/jira/browse/SLING-11517 a dedicated 
class is necessary
@@ -138,8 +144,26 @@ public class JcrResourceProvider extends 
ResourceProvider<JcrProviderState> {
 
     private final AtomicReference<URIProvider[]> uriProviderReference = new 
AtomicReference<>();
 
+    private static boolean idAddressing;
+
+    @ObjectClassDefinition(
+            name = "Apache Sling JCR Resource Provider",
+            description = "The JCR Resource Provider provides access to the 
JCR repository."
+
+    )
+    @interface Configuration {
+
+        @AttributeDefinition(
+                name = "Resource Addressing by ID",
+                description = "If enabled, the resource provider will enable 
addressing resources by their JCR UUID " +
+                        "by using the special path prefix '/jcr:id/'."
+        )
+        boolean resource_addressingById() default false;
+
+    }
+
     @Activate
-    protected void activate(final ComponentContext context) {
+    protected void activate(final ComponentContext context, final 
Configuration configuration) {
         SlingRepository slingRepository = 
context.locateService(REPOSITORY_REFERENCE_NAME,
                 this.repositoryReference);
         if (slingRepository == null) {
@@ -154,6 +178,8 @@ public class JcrResourceProvider extends 
ResourceProvider<JcrProviderState> {
 
         this.stateFactory = new JcrProviderStateFactory(repositoryReference, 
slingRepository,
                 classLoaderManagerReference, uriProviderReference);
+
+        idAddressing = configuration.resource_addressingById();
     }
 
     @Deactivate
@@ -217,6 +243,14 @@ public class JcrResourceProvider extends 
ResourceProvider<JcrProviderState> {
         this.updateListeners();
     }
 
+    /**
+     * Check if ID addressing is enabled.
+     * @return {@code true} if ID addressing is enabled, {@code false} 
otherwise.
+     */
+    public static boolean isIdAddressingEnabled() {
+        return idAddressing;
+    }
+
     @SuppressWarnings("unused")
     private void bindRepository(final ServiceReference<SlingRepository> ref) {
         this.repositoryReference = ref;
diff --git 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactoryTest.java
 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactoryTest.java
index e01918d..0164483 100644
--- 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactoryTest.java
+++ 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrItemResourceFactoryTest.java
@@ -18,12 +18,8 @@
  */
 package org.apache.sling.jcr.resource.internal.helper.jcr;
 
-import org.apache.jackrabbit.JcrConstants;
-import org.apache.jackrabbit.commons.JcrUtils;
-import 
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
-import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
-import org.apache.sling.jcr.resource.internal.HelperData;
+import java.lang.reflect.Proxy;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.jcr.GuestCredentials;
 import javax.jcr.Item;
@@ -31,8 +27,16 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.security.Privilege;
-import java.lang.reflect.Proxy;
-import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
+import 
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.sling.jcr.resource.internal.HelperData;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.osgi.service.component.ComponentContext;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -42,8 +46,10 @@ public class JcrItemResourceFactoryTest extends 
SlingRepositoryTestBase {
     public static final String EXISTING_NODE_PATH = "/existing";
     public static final String NON_EXISTING_NODE_PATH = "/nonexisting";
     public static final String NON_ABSOLUTE_PATH = "invalidpath";
+    public static final String REFERENCEABLE_NODE_PATH = "/referenceable";
 
     private Node node;
+    private Node referenceableNode;
     private Session nonJackrabbitSession;
 
     @Override
@@ -51,6 +57,8 @@ public class JcrItemResourceFactoryTest extends 
SlingRepositoryTestBase {
         super.setUp();
         final Session session = getSession();
         node = JcrUtils.getOrCreateByPath(EXISTING_NODE_PATH, 
"nt:unstructured", session);
+        referenceableNode = 
JcrUtils.getOrCreateByPath(REFERENCEABLE_NODE_PATH, "nt:unstructured", session);
+        referenceableNode.addMixin(JcrConstants.MIX_REFERENCEABLE);
         session.save();
 
         nonJackrabbitSession = (Session) Proxy.newProxyInstance(
@@ -139,6 +147,42 @@ public class JcrItemResourceFactoryTest extends 
SlingRepositoryTestBase {
         compareGetParentOrNull(s, EXISTING_NODE_PATH, true);
     }
 
+    public void testGetNodeByIdentifierConfigEnabled() throws Exception {
+        ComponentContext ctx = mock(ComponentContext.class);
+        when(ctx.locateService(ArgumentMatchers.anyString(), 
Mockito.any())).thenReturn(SlingRepositoryProvider.getRepository());
+
+        JcrResourceProvider.Configuration configuration = 
mock(JcrResourceProvider.Configuration.class);
+        when(configuration.resource_addressingById()).thenReturn(true);
+        JcrResourceProvider jcrResourceProvider = new JcrResourceProvider();
+        jcrResourceProvider.activate(ctx, configuration);
+
+        HelperData helper = new HelperData(new AtomicReference<>(), new 
AtomicReference<>());
+        String identifier = referenceableNode.getIdentifier();
+        String uuid = 
referenceableNode.getProperty(JcrConstants.JCR_UUID).getString();
+        assertEquals(identifier, uuid);
+        Item referenceableItem =
+                new JcrItemResourceFactory(session, 
helper).getItemOrNull(JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
referenceableNode.getIdentifier());
+        assertNotNull(referenceableItem);
+        assertTrue(referenceableItem.isNode());
+        assertEquals(REFERENCEABLE_NODE_PATH, referenceableItem.getPath());
+
+        // the node identifier in this case is the path
+        Item nodeItem = new JcrItemResourceFactory(session, 
helper).getItemOrNull(JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
node.getIdentifier());
+        assertNotNull(nodeItem);
+        assertTrue(nodeItem.isNode());
+        assertEquals(EXISTING_NODE_PATH, nodeItem.getPath());
+    }
+
+    public void testGetNodeByIdentifierConfigNotEnabled() throws Exception {
+        HelperData helper = new HelperData(new AtomicReference<>(), new 
AtomicReference<>());
+        String identifier = referenceableNode.getIdentifier();
+        String uuid = 
referenceableNode.getProperty(JcrConstants.JCR_UUID).getString();
+        assertEquals(identifier, uuid);
+        Item referenceableItem =
+                new JcrItemResourceFactory(session, 
helper).getItemOrNull(JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
referenceableNode.getIdentifier());
+        assertNull(referenceableItem);
+    }
+
     private void compareGetParentOrNull(Session s, String path, boolean 
nullExpected) throws RepositoryException {
         HelperData helper = new HelperData(new AtomicReference<>(), new 
AtomicReference<>());
 
diff --git 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
index 433d8c8..657170b 100644
--- 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
+++ 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
@@ -18,18 +18,6 @@
  */
 package org.apache.sling.jcr.resource.internal.helper.jcr;
 
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeThat;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -59,6 +47,18 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.component.ComponentContext;
 
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 @RunWith(Parameterized.class)
 public class JcrResourceProviderSessionHandlingTest {
 
@@ -227,8 +227,10 @@ public class JcrResourceProviderSessionHandlingTest {
         ComponentContext ctx = mock(ComponentContext.class);
         when(ctx.locateService(ArgumentMatchers.anyString(), 
Mockito.any())).thenReturn(repo);
 
+        JcrResourceProvider.Configuration configuration = 
mock(JcrResourceProvider.Configuration.class);
+
         jcrResourceProvider = new JcrResourceProvider();
-        jcrResourceProvider.activate(ctx);
+        jcrResourceProvider.activate(ctx, configuration);
 
         jcrProviderState = jcrResourceProvider.authenticate(authInfo);
     }
diff --git 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
index ae2b088..3be3934 100644
--- 
a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
+++ 
b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
@@ -18,10 +18,6 @@
  */
 package org.apache.sling.jcr.resource.internal.helper.jcr;
 
-import static javax.jcr.nodetype.NodeType.NT_UNSTRUCTURED;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 import java.security.Principal;
 import java.util.HashMap;
 import java.util.Map;
@@ -32,9 +28,11 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.jcr.resource.api.JcrResourceConstants;
 import org.apache.sling.jcr.resource.internal.HelperData;
 import org.apache.sling.spi.resource.provider.ResolveContext;
@@ -45,14 +43,21 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.osgi.service.component.ComponentContext;
 
+import static javax.jcr.nodetype.NodeType.NT_UNSTRUCTURED;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 @RunWith(MockitoJUnitRunner.class)
 public class JcrResourceProviderTest extends SlingRepositoryTestBase {
 
     JcrResourceProvider jcrResourceProvider;
     Session session;
+    ComponentContext ctx;
 
     @Override
     @Before
@@ -60,9 +65,11 @@ public class JcrResourceProviderTest extends 
SlingRepositoryTestBase {
         super.setUp();
         // create the session
         session = getSession();
-        ComponentContext ctx = mock(ComponentContext.class);
+        ctx = mock(ComponentContext.class);
+        when(ctx.locateService(ArgumentMatchers.anyString(), 
Mockito.any())).thenReturn(SlingRepositoryProvider.getRepository());
+        JcrResourceProvider.Configuration configuration = 
mock(JcrResourceProvider.Configuration.class);
         jcrResourceProvider = new JcrResourceProvider();
-        jcrResourceProvider.activate(ctx);
+        jcrResourceProvider.activate(ctx, configuration);
     }
 
     @Override
@@ -86,6 +93,7 @@ public class JcrResourceProviderTest extends 
SlingRepositoryTestBase {
     private @NotNull ResolveContext mockResolveContext() {
         ResolveContext ctx = mock(ResolveContext.class);
         when(ctx.getProviderState()).thenReturn(createProviderState());
+        
when(ctx.getResourceResolver()).thenReturn(mock(ResourceResolver.class));
         return ctx;
     }
 
@@ -213,6 +221,46 @@ public class JcrResourceProviderTest extends 
SlingRepositoryTestBase {
         
assertEquals("admin",grandchild.getItem().getProperty("jcr:createdBy").getString());
         
     }
+
+    @Test
+    public void getResourceByIdentifierConfigurationEnabled() throws 
RepositoryException {
+        JcrResourceProvider.Configuration configuration = 
mock(JcrResourceProvider.Configuration.class);
+        when(configuration.resource_addressingById()).thenReturn(true);
+        jcrResourceProvider.activate(ctx, configuration);
+
+        Node referenceable = JcrUtils.getOrCreateByPath("/root/referenceable", 
JcrConstants.NT_UNSTRUCTURED, session);
+        referenceable.addMixin(JcrConstants.MIX_REFERENCEABLE);
+        Node nonReferenceable = 
JcrUtils.getOrCreateByPath("/root/non-referenceable", 
JcrConstants.NT_UNSTRUCTURED,
+                session);
+        session.save();
+
+        Resource referenceableResource = 
jcrResourceProvider.getResource(mockResolveContext(),
+                JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
referenceable.getIdentifier(), ResourceContext.EMPTY_CONTEXT, null);
+        assertNotNull(referenceableResource);
+        assertEquals(referenceableResource.getPath(), referenceable.getPath());
+
+        Resource nonReferenceableResource = 
jcrResourceProvider.getResource(mockResolveContext(),
+                JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
nonReferenceable.getIdentifier(), ResourceContext.EMPTY_CONTEXT, null);
+        assertNotNull(nonReferenceableResource);
+        assertEquals(nonReferenceableResource.getPath(), 
nonReferenceable.getPath());
+    }
+
+    @Test
+    public void getResourceByIdentifierConfigurationNotEnabled() throws 
RepositoryException {
+        Node referenceable = JcrUtils.getOrCreateByPath("/root/referenceable", 
JcrConstants.NT_UNSTRUCTURED, session);
+        referenceable.addMixin(JcrConstants.MIX_REFERENCEABLE);
+        Node nonReferenceable = 
JcrUtils.getOrCreateByPath("/root/non-referenceable", 
JcrConstants.NT_UNSTRUCTURED,
+                session);
+        session.save();
+
+        Resource referenceableResource = 
jcrResourceProvider.getResource(mockResolveContext(),
+                JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
referenceable.getIdentifier(), ResourceContext.EMPTY_CONTEXT, null);
+        assertNull(referenceableResource);
+
+        Resource nonReferenceableResource = 
jcrResourceProvider.getResource(mockResolveContext(),
+                JcrItemResourceFactory.SEARCH_BY_ID_PREFIX + 
nonReferenceable.getIdentifier(), ResourceContext.EMPTY_CONTEXT, null);
+        assertNull(nonReferenceableResource);
+    }
     
 
     private static void assertResources(Resource... resources) {

Reply via email to