You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/07/09 15:59:36 UTC

(ws-neethi) branch coheigea/circular-reference-no-id created (now 39870cf)

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

coheigea pushed a change to branch coheigea/circular-reference-no-id
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git


      at 39870cf  Detect cycles when there is no Policy Id

This branch includes the following new commits:

     new 39870cf  Detect cycles when there is no Policy Id

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(ws-neethi) 01/01: Detect cycles when there is no Policy Id

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/circular-reference-no-id
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git

commit 39870cf545820905939359d9ae8017b00d524e0c
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Jul 9 16:59:06 2026 +0100

    Detect cycles when there is no Policy Id
---
 .../org/apache/neethi/AbstractPolicyOperator.java  | 20 +++++---
 .../org/apache/neethi/PolicyReferenceTest.java     | 56 +++++++++++++++++-----
 2 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
index bdac65a..b4f920c 100644
--- a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
+++ b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
@@ -138,6 +138,9 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
                 }
             } else if (policyComponent.getType() == Constants.TYPE_POLICY_REF) {
                 String uri = ((PolicyReference) policyComponent).getURI();
+                if (uri == null || uri.length() == 0) {
+                    throw new RuntimeException("PolicyReference URI is null or empty");
+                }
                 policyComponent = reg == null ? null : reg.lookup(uri);
                 if (policyComponent == null && uri.charAt(0) == '#') {
                     String id = uri.substring(1);
@@ -155,14 +158,17 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
                     throw new RuntimeException(uri + " can't be resolved");
                 }
                 String resolvedId = ((Policy) policyComponent).getId();
-                if (resolvedId != null && !resolving.add(resolvedId)) {
-                    throw new RuntimeException("Circular PolicyReference detected: " + resolvedId);
+                String resolvingToken = resolvedId != null && resolvedId.length() > 0
+                    ? "id:" + resolvedId : "uri:" + uri;
+                if (!resolving.add(resolvingToken)) {
+                    throw new RuntimeException("Circular PolicyReference detected: " + resolvingToken);
                 }
-                All all = new All();
-                all.addPolicyComponents(((Policy) policyComponent).getPolicyComponents());
-                childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, resolving));
-                if (resolvedId != null) {
-                    resolving.remove(resolvedId);
+                try {
+                    All all = new All();
+                    all.addPolicyComponents(((Policy) policyComponent).getPolicyComponents());
+                    childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, resolving));
+                } finally {
+                    resolving.remove(resolvingToken);
                 }
          
             } else if (policyComponent.getType() == Constants.TYPE_POLICY) {
diff --git a/src/test/java/org/apache/neethi/PolicyReferenceTest.java b/src/test/java/org/apache/neethi/PolicyReferenceTest.java
index e0d0c46..b5b99a8 100644
--- a/src/test/java/org/apache/neethi/PolicyReferenceTest.java
+++ b/src/test/java/org/apache/neethi/PolicyReferenceTest.java
@@ -164,19 +164,10 @@ public class PolicyReferenceTest extends PolicyTestCase {
     }
 
     /**
-     * Test two policies that mutually reference each other cause unbounded recursion in
-     * AbstractPolicyOperator.normalizeOperator() when normalize(true) is called.
-     *
-     * Policy A contains a PolicyReference to "B".
-     * Policy B contains a PolicyReference to "A".
-     * Both are registered in the PolicyRegistry before normalization begins.
-     *
-     * normalizeOperator() resolves each PolicyReference via reg.lookup() and
-     * recurses into the resolved policy's components with no cycle detection,
-     * ultimately throwing StackOverflowError.
+     * Ensures circular references are detected for policies with explicit Id values.
      */
     @Test
-    public void testCircularPolicyReferenceThrowsStackOverflow() {
+    public void testCircularPolicyReferenceWithIdsIsDetected() {
         // Build Policy A: ExactlyOne > All > PolicyReference("B")
         PolicyReference refToB = new PolicyReference();
         refToB.setURI("B");
@@ -204,7 +195,48 @@ public class PolicyReferenceTest extends PolicyTestCase {
 
         try {
             policyA.normalize(registry, true);
-            fail("Expected StackOverflowError or RuntimeException due to circular reference");
+            fail("Expected RuntimeException due to circular reference");
+        } catch (RuntimeException e) {
+            assertTrue(
+                "Expected a cycle-detection message but got: " + e.getMessage(),
+                e.getMessage() != null && e.getMessage().toLowerCase().contains("circular"));
+        }
+    }
+
+    /**
+     * Ensures circular references are detected even when referenced policies have no Id.
+     *
+     * Cycle detection must use a stable non-null resolution token so that
+     * no-Id references cannot bypass the resolving set.
+     */
+    @Test
+    public void testCircularPolicyReferenceWithoutIdsIsDetected() {
+        // Policy A references registry key "B" but has no policy Id.
+        PolicyReference refToB = new PolicyReference();
+        refToB.setURI("B");
+        All allA = new All();
+        allA.addPolicyComponent(refToB);
+        ExactlyOne eoA = new ExactlyOne();
+        eoA.addPolicyComponent(allA);
+        Policy policyA = new Policy(registry);
+        policyA.addPolicyComponent(eoA);
+
+        // Policy B references registry key "A" and also has no policy Id.
+        PolicyReference refToA = new PolicyReference();
+        refToA.setURI("A");
+        All allB = new All();
+        allB.addPolicyComponent(refToA);
+        ExactlyOne eoB = new ExactlyOne();
+        eoB.addPolicyComponent(allB);
+        Policy policyB = new Policy(registry);
+        policyB.addPolicyComponent(eoB);
+
+        registry.register("A", policyA);
+        registry.register("B", policyB);
+
+        try {
+            policyA.normalize(registry, true);
+            fail("Expected RuntimeException due to circular reference");
         } catch (RuntimeException e) {
             assertTrue(
                 "Expected a cycle-detection message but got: " + e.getMessage(),