diff --git a/PR.md b/PR.md
new file mode 100644
index 000000000..3f8758dbc
--- /dev/null
+++ b/PR.md
@@ -0,0 +1,15 @@
+## Summary
+- prevent circular `referredSemanticId` chains in `DefaultReference` by rejecting self/indirect cycles during assignment
+- add unit tests that cover self and indirect cycles plus happy-path acyclic chains
+- include JUnit dependency in the model module to compile the new tests
+
+## Background
+Jackson XML serialization blew up with a StackOverflow when `Reference.referredSemanticId` contained a cycle (e.g., `Reference -> Reference -> same Reference`). The model allowed such graphs because the setter did not guard against cycles. This PR keeps the model acyclic so downstream serializers do not need custom circular-reference handling.
+
+## Implementation Notes
+- cycle detection uses identity (not equals) to avoid false positives from structurally equal references
+- the guard walks the `referredSemanticId` chain once and fails fast with a clear `IllegalArgumentException` message
+- tests cover self-reference, indirect cycles, and a valid chain
+
+## Testing
+- `mvn -pl model test -Dmaven.repo.local=./.m2`
diff --git a/model/pom.xml b/model/pom.xml
index db21bf29e..ab2226b22 100644
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -17,6 +17,13 @@
This project includes a Java representation of the classes defined in the Asset Administration Shell ontology.
Asset Administration Shell Java Model
+
+
+ junit
+ junit
+ test
+
+
1.8
diff --git a/model/src/main/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReference.java b/model/src/main/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReference.java
index a19a091e5..e5ab804fd 100644
--- a/model/src/main/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReference.java
+++ b/model/src/main/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReference.java
@@ -16,8 +16,11 @@
package org.eclipse.digitaltwin.aas4j.v3.model.impl;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import org.eclipse.digitaltwin.aas4j.v3.model.Key;
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes;
@@ -93,6 +96,7 @@ public Reference getReferredSemanticId() {
@Override
public void setReferredSemanticId(Reference referredSemanticId) {
+ validateReferredSemanticId(referredSemanticId);
this.referredSemanticId = referredSemanticId;
}
@@ -119,4 +123,19 @@ protected DefaultReference newBuildingInstance() {
return new DefaultReference();
}
}
+
+ private void validateReferredSemanticId(Reference referredSemanticId) {
+ if (referredSemanticId == null) {
+ return;
+ }
+ Set visited = Collections.newSetFromMap(new IdentityHashMap<>());
+ Reference current = referredSemanticId;
+ while (current != null) {
+ if (current == this || !visited.add(current)) {
+ throw new IllegalArgumentException(
+ "referredSemanticId must not create a circular Reference");
+ }
+ current = current.getReferredSemanticId();
+ }
+ }
}
diff --git a/model/src/test/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReferenceTest.java b/model/src/test/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReferenceTest.java
new file mode 100644
index 000000000..b64e244cb
--- /dev/null
+++ b/model/src/test/java/org/eclipse/digitaltwin/aas4j/v3/model/impl/DefaultReferenceTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V.
+ * Copyright (c) 2023, SAP SE or an SAP affiliate company
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.eclipse.digitaltwin.aas4j.v3.model.impl;
+
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+public class DefaultReferenceTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setReferredSemanticId_withSelfReference_throws() {
+ DefaultReference reference = new DefaultReference();
+
+ reference.setReferredSemanticId(reference);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setReferredSemanticId_withIndirectCycle_throws() {
+ DefaultReference first = new DefaultReference();
+ DefaultReference second = new DefaultReference();
+ second.setReferredSemanticId(first);
+
+ first.setReferredSemanticId(second);
+ }
+
+ @Test
+ public void setReferredSemanticId_withAcyclicChain_succeeds() {
+ DefaultReference reference = new DefaultReference();
+ DefaultReference semanticReference = new DefaultReference();
+ DefaultReference semanticParent = new DefaultReference();
+
+ semanticReference.setReferredSemanticId(semanticParent);
+ reference.setReferredSemanticId(semanticReference);
+
+ assertSame(semanticReference, reference.getReferredSemanticId());
+ assertSame(semanticParent, semanticReference.getReferredSemanticId());
+ }
+}