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:37 UTC
(ws-neethi) 01/01: Detect cycles when there is no Policy Id
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(),