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) {