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

reschke pushed a commit to branch OAK-11657c
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit 0ec55dda83e9fa2ffa2064d4dd3b33d6df843fef
Author: Julian Reschke <[email protected]>
AuthorDate: Thu May 22 20:57:19 2025 +0100

    OAK-11657: JackrabbitSession.getExpandedName/Path need to return stable 
expanded names, getExpandedName needs to fail for broken namespace names
---
 .../apache/jackrabbit/oak/namepath/NameMapper.java |  4 +--
 .../oak/namepath/impl/GlobalNameMapper.java        | 28 ++++++++++------
 .../apache/jackrabbit/api/JackrabbitSession.java   |  9 +++++-
 .../jackrabbit/oak/jcr/session/SessionImpl.java    |  7 +++-
 .../oak/jcr/session/JackrabbitSessionTest.java     | 37 +++++++++++++++++++---
 5 files changed, 67 insertions(+), 18 deletions(-)

diff --git 
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java 
b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java
index 94c8d9bf84..dc94f4d706 100644
--- 
a/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java
+++ 
b/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/namepath/NameMapper.java
@@ -87,11 +87,11 @@ public interface NameMapper {
      * @param oakName Oak name
      * @return JCR name in expanded form
      * @since Oak 1.78.0
-     * @throws IllegalStateException in case the namespace URI for the given 
Oak name cannot be resolved
+     * @throws IllegalStateException in case the namespace URI for the given
+     * Oak name cannot be resolved or is invalid
      * 
      * @see <a 
href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form";>JCR
 2.0, 3.2.5.1 Expanded Form</a>
      */
     @NotNull
     String getExpandedJcrName(@NotNull String oakName);
-
 }
diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java
index 546d9aab13..ec26596ee5 100644
--- 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/impl/GlobalNameMapper.java
@@ -35,6 +35,7 @@ import static 
org.apache.jackrabbit.oak.spi.namespace.NamespaceConstants.REP_URI
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.jcr.NamespaceException;
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.oak.api.Root;
@@ -65,16 +66,17 @@ public class GlobalNameMapper implements NameMapper {
         return name.startsWith(":");
     }
 
+    private static boolean isValidNamespaceName(String namespace) {
+        // the empty namespace and "internal" are valid as well, otherwise it 
always contains a colon (as it is a URI)
+        // compare with RFC 3986, Section 3 
(https://datatracker.ietf.org/doc/html/rfc3986#section-3)
+        return namespace.isEmpty() || 
namespace.equals(NamespaceConstants.NAMESPACE_REP) || namespace.contains(":");
+    }
+
     protected static boolean isExpandedName(String name) {
         if (name.startsWith("{")) {
             int brace = name.indexOf('}', 1);
             if (brace != -1) {
-                String namespace = name.substring(1, brace);
-                // the empty namespace and "internal" are valid as well, 
otherwise it always contains a colon (as it is a URI)
-                // compare with RFC 3986, Section 3 
(https://datatracker.ietf.org/doc/html/rfc3986#section-3)
-                if (namespace.isEmpty() || 
namespace.equals(NamespaceConstants.NAMESPACE_REP)|| namespace.indexOf(':') != 
-1) {
-                    return true;
-                }
+                return isValidNamespaceName(name.substring(1, brace));
             }
         }
         return false;
@@ -143,17 +145,23 @@ public class GlobalNameMapper implements NameMapper {
         int colon = oakName.indexOf(':');
         if (colon > 0) {
             String oakPrefix = oakName.substring(0, colon);
-            // local mapping must take precedence...
-            uri = getSessionLocalMappings().get(oakPrefix);
+            uri = getNamespacesProperty(oakPrefix);
+            // global mapping must take precedence...
             if (uri == null) {
-                // ...over global mappings
-                uri = getNamespacesProperty(oakPrefix);
+                // ...over local mappings
+                uri = getSessionLocalMappings().get(oakPrefix);
             }
             if (uri == null) {
                 throw new IllegalStateException(
                         "No namespace mapping found for " + oakName);
             }
             localName = oakName.substring(colon + 1);
+            // check namespace name for validity in Oak
+            if (!isValidNamespaceName(uri)) {
+                throw new IllegalStateException(
+                    new NamespaceException("Cannot determine expanded name for 
'" + oakName +
+                        "' as registered namespace name '" + uri + "' is 
invalid"));
+            }
         } else {
             uri = "";
             localName = oakName;
diff --git 
a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
 
b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
index db1bf7cb0d..f7e1f71187 100644
--- 
a/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
+++ 
b/oak-jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitSession.java
@@ -25,6 +25,7 @@ import javax.jcr.Node;
 import javax.jcr.Property;
 import javax.jcr.Session;
 import javax.jcr.AccessDeniedException;
+import javax.jcr.NamespaceException;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 
@@ -282,7 +283,10 @@ public interface JackrabbitSession extends Session {
      * Returns the expanded name of the given {@code Item}.
      * @param item the item for which to retrieve the name
      * @return the name of the item in expanded form.
-     * @throws RepositoryException if another error occurs.
+     * @throws NamespaceException when no expanded name can
+     * be determined (for instance, if the registered namespace
+     * (name) is invalid)
+     * @throws RepositoryException if another error occurs
      * @since 1.78.0
      * @see <a 
href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form";>JCR
 2.0, 3.2.5.1 Expanded Form</a>
      */
@@ -292,6 +296,9 @@ public interface JackrabbitSession extends Session {
      * Returns the expanded path of the given {@code Item}.
      * @param item the item for which to retrieve the name
      * @return the path of the item in expanded form.
+     * @throws NamespaceException when no expanded name can
+     * be determined (for instance, if the registered namespace
+     * (name) is invalid)
      * @throws RepositoryException if another error occurs.
      * @since 1.78.0
      * @see <a 
href="https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.2.5.1%20Expanded%20Form";>JCR
 2.0, 3.2.5.1 Expanded Form</a>
diff --git 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
index 5eb76868cb..6f37d57b0b 100644
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
+++ 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
@@ -860,7 +860,12 @@ public class SessionImpl implements JackrabbitSession {
             ItemImpl<?> itemImpl = checkItemImpl(item);
             return 
itemImpl.sessionContext.getExpandedJcrName(itemImpl.getOakName());
         } catch (IllegalStateException e) {
-            throw new RepositoryException("Namespace exception " + 
e.getMessage());
+            // unwrap RepositoryException when available
+            if (e.getCause() instanceof RepositoryException) {
+                throw (RepositoryException) e.getCause();
+            } else {
+                throw new RepositoryException("Namespace exception " + 
e.getMessage());
+            }
         }
     }
 
diff --git 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
index f7c86562ad..926502a382 100644
--- 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
+++ 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/JackrabbitSessionTest.java
@@ -24,10 +24,13 @@ import org.jetbrains.annotations.Nullable;
 
 import javax.jcr.GuestCredentials;
 import javax.jcr.Item;
+import javax.jcr.NamespaceException;
 import javax.jcr.Node;
 import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 
+import java.util.UUID;
+
 import static org.mockito.Mockito.mock;
 
 public class JackrabbitSessionTest extends AbstractJCRTest {
@@ -86,20 +89,46 @@ public class JackrabbitSessionTest extends AbstractJCRTest {
         assertEquals("{}testroot", s.getExpandedName(testRootNode));
         Node n = testRootNode.addNode("test:bar");
         assertEquals("{http://www.apache.org/jackrabbit/test}bar";, 
s.getExpandedName(n));
-        // now remap namespace uri
+        // now remap namespace uri - should not affect expanded name
+        assertEquals("prefix 'test' has unexpected mapping",
+                "http://www.apache.org/jackrabbit/test";, 
s.getNamespaceURI("test"));
         s.setNamespacePrefix("test", "urn:foo");
-        assertEquals("{urn:foo}bar", s.getExpandedName(n));
+        assertEquals("{http://www.apache.org/jackrabbit/test}bar";, 
s.getExpandedName(n));
         // use special namespace uri
         n = testRootNode.addNode("rep:bar");
         assertEquals("{internal}bar", s.getExpandedName(n));
     }
 
+    public void testGetExpandedNameBrokenNamespace() throws 
RepositoryException {
+        // empty namespace uri
+        assertEquals("{}testroot", s.getExpandedName(testRootNode));
+
+        String randomNamespacePrefix = "prefix-" + UUID.randomUUID();
+        // below is not a valid namespace a.k.a. namespace URI
+        String randomNamespaceName = "name-" + UUID.randomUUID();
+
+        // register broken namespace prefix/name mapping
+        
s.getWorkspace().getNamespaceRegistry().registerNamespace(randomNamespacePrefix,
 randomNamespaceName);
+
+        try {
+            Node n = testRootNode.addNode(randomNamespacePrefix + ":qux");
+
+            // there is no expanded name, thus we expect an exception here
+            String result = s.getExpandedName(n);
+            fail("there is no expanded name in this case, so we expect the 
call to fail, however we get: " + result);
+        } catch (NamespaceException ex) {
+            // expected
+        } finally {
+            
s.getWorkspace().getNamespaceRegistry().unregisterNamespace(randomNamespacePrefix);
+        }
+    }
+
     public void testGetExpandedPath() throws RepositoryException {
         assertEquals("/{}testroot", s.getExpandedPath(testRootNode));
         Node n = testRootNode.addNode("test:bar").addNode("rep:bar");
         
assertEquals("/{}testroot/{http://www.apache.org/jackrabbit/test}bar/{internal}bar";,
 s.getExpandedPath(n));
-        // now remap namespace uri
+        // now remap namespace uri - should not affect expanded name
         s.setNamespacePrefix("test", "urn:foo");
-        assertEquals("/{}testroot/{urn:foo}bar/{internal}bar", 
s.getExpandedPath(n));
+        
assertEquals("/{}testroot/{http://www.apache.org/jackrabbit/test}bar/{internal}bar";,
 s.getExpandedPath(n));
     }
 }
\ No newline at end of file

Reply via email to