Skip to content

Commit a58966b

Browse files
committed
TEDEFO-4821: Add hierarchy traversal and repeatability to SDK entities
- SdkNode: Add parent reference and getAncestry() with caching - SdkField: Add repeatable property and parentNode reference - SdkNodeRepository: Two-pass loading to wire parent relationships - SdkFieldRepository: Optional parentNode wiring via SdkNodeRepository - MapFromJson: Context-aware constructor for dependency injection
1 parent dd51af2 commit a58966b

6 files changed

Lines changed: 130 additions & 8 deletions

File tree

src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public abstract class SdkField implements Comparable<SdkField> {
1010
private final String parentNodeId;
1111
private final String type;
1212
private final String codelistId;
13+
private final boolean repeatable;
14+
private SdkNode parentNode;
1315

1416
@SuppressWarnings("unused")
1517
private SdkField() {
@@ -18,12 +20,19 @@ private SdkField() {
1820

1921
protected SdkField(final String id, final String type, final String parentNodeId,
2022
final String xpathAbsolute, final String xpathRelative, final String codelistId) {
23+
this(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId, false);
24+
}
25+
26+
protected SdkField(final String id, final String type, final String parentNodeId,
27+
final String xpathAbsolute, final String xpathRelative, final String codelistId,
28+
final boolean repeatable) {
2129
this.id = id;
2230
this.parentNodeId = parentNodeId;
2331
this.xpathAbsolute = xpathAbsolute;
2432
this.xpathRelative = xpathRelative;
2533
this.type = type;
2634
this.codelistId = codelistId;
35+
this.repeatable = repeatable;
2736
}
2837

2938
protected SdkField(final JsonNode fieldNode) {
@@ -33,6 +42,7 @@ protected SdkField(final JsonNode fieldNode) {
3342
this.xpathRelative = fieldNode.get("xpathRelative").asText(null);
3443
this.type = fieldNode.get("type").asText(null);
3544
this.codelistId = extractCodelistId(fieldNode);
45+
this.repeatable = extractRepeatable(fieldNode);
3646
}
3747

3848
protected String extractCodelistId(final JsonNode fieldNode) {
@@ -49,6 +59,20 @@ protected String extractCodelistId(final JsonNode fieldNode) {
4959
return valueNode.get("id").asText(null);
5060
}
5161

62+
protected boolean extractRepeatable(final JsonNode fieldNode) {
63+
final JsonNode repeatableNode = fieldNode.get("repeatable");
64+
if (repeatableNode == null) {
65+
return false;
66+
}
67+
68+
final JsonNode valueNode = repeatableNode.get("value");
69+
if (valueNode == null) {
70+
return false;
71+
}
72+
73+
return valueNode.asBoolean(false);
74+
}
75+
5276
public String getId() {
5377
return id;
5478
}
@@ -73,6 +97,18 @@ public String getCodelistId() {
7397
return codelistId;
7498
}
7599

100+
public boolean isRepeatable() {
101+
return repeatable;
102+
}
103+
104+
public SdkNode getParentNode() {
105+
return parentNode;
106+
}
107+
108+
public void setParentNode(SdkNode parentNode) {
109+
this.parentNode = parentNode;
110+
}
111+
76112
/**
77113
* Helps with hash maps collisions. Should be consistent with equals.
78114
*/

src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package eu.europa.ted.eforms.sdk.entity;
22

3+
import java.util.Collections;
4+
import java.util.LinkedHashSet;
35
import java.util.Objects;
6+
import java.util.Set;
47
import com.fasterxml.jackson.databind.JsonNode;
58

69
/**
@@ -12,6 +15,8 @@ public abstract class SdkNode implements Comparable<SdkNode> {
1215
private final String xpathRelative;
1316
private final String parentId;
1417
private final boolean repeatable;
18+
private SdkNode parent;
19+
private Set<String> cachedAncestry;
1520

1621
protected SdkNode(final String id, final String parentId, final String xpathAbsolute,
1722
final String xpathRelative, final boolean repeatable) {
@@ -51,9 +56,31 @@ public boolean isRepeatable() {
5156
return repeatable;
5257
}
5358

59+
public SdkNode getParent() {
60+
return parent;
61+
}
62+
63+
public void setParent(SdkNode parent) {
64+
this.parent = parent;
65+
this.cachedAncestry = null;
66+
}
67+
68+
public Set<String> getAncestry() {
69+
if (cachedAncestry == null) {
70+
Set<String> ancestry = new LinkedHashSet<>();
71+
SdkNode current = this;
72+
while (current != null) {
73+
ancestry.add(current.getId());
74+
current = current.getParent();
75+
}
76+
cachedAncestry = Collections.unmodifiableSet(ancestry);
77+
}
78+
return cachedAncestry;
79+
}
80+
5481
@Override
5582
public int compareTo(SdkNode o) {
56-
return o.getId().compareTo(o.getId());
83+
return this.getId().compareTo(o.getId());
5784
}
5885

5986
@Override

src/main/java/eu/europa/ted/eforms/sdk/repository/MapFromJson.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,20 @@ protected MapFromJson(final String sdkVersion, final Path jsonPath)
3939
}
4040
}
4141

42-
private final void populateMap(final Path jsonPath) throws IOException, InstantiationException {
42+
protected MapFromJson(final String sdkVersion, final Path jsonPath, final Object... context)
43+
throws InstantiationException {
44+
this.sdkVersion = sdkVersion;
45+
46+
try {
47+
populateMap(jsonPath, context);
48+
} catch (IOException e) {
49+
throw new RuntimeException(MessageFormat
50+
.format("Failed to set resource filepath to [{0}]. Error was: {1}", jsonPath, e));
51+
}
52+
}
53+
54+
private final void populateMap(final Path jsonPath, final Object... context)
55+
throws IOException, InstantiationException {
4356
logger.debug("Populating maps for context, jsonPath={}", jsonPath);
4457

4558
final ObjectMapper mapper = buildStandardJacksonObjectMapper();
@@ -54,12 +67,24 @@ private final void populateMap(final Path jsonPath) throws IOException, Instanti
5467
}
5568

5669
final JsonNode json = mapper.readTree(fieldsJsonInputStream);
57-
populateMap(json);
70+
populateMap(json, context);
5871
}
5972
}
6073

74+
/**
75+
* Abstract method for populating the map from JSON. Existing subclasses implement this.
76+
*/
6177
protected abstract void populateMap(final JsonNode json) throws InstantiationException;
6278

79+
/**
80+
* Context-aware population method. Default implementation delegates to the abstract method,
81+
* ignoring context. Subclasses that need context should override this method.
82+
*/
83+
protected void populateMap(final JsonNode json, final Object... context)
84+
throws InstantiationException {
85+
populateMap(json);
86+
}
87+
6388
/**
6489
* @return A reusable Jackson object mapper instance.
6590
*/

src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,31 @@ public SdkFieldRepository(String sdkVersion, Path jsonPath) throws Instantiation
1414
super(sdkVersion, jsonPath);
1515
}
1616

17+
public SdkFieldRepository(String sdkVersion, Path jsonPath, SdkNodeRepository nodeRepository)
18+
throws InstantiationException {
19+
super(sdkVersion, jsonPath, nodeRepository);
20+
}
21+
1722
@Override
1823
protected void populateMap(final JsonNode json) throws InstantiationException {
24+
populateMap(json, new Object[0]);
25+
}
26+
27+
@Override
28+
protected void populateMap(final JsonNode json, final Object... context)
29+
throws InstantiationException {
30+
SdkNodeRepository nodes = (context.length > 0 && context[0] instanceof SdkNodeRepository)
31+
? (SdkNodeRepository) context[0]
32+
: null;
33+
1934
final ArrayNode fields = (ArrayNode) json.get(SdkConstants.FIELDS_JSON_FIELDS_KEY);
2035
for (final JsonNode field : fields) {
2136
final SdkField sdkField = SdkEntityFactory.getSdkField(sdkVersion, field);
2237
put(sdkField.getId(), sdkField);
38+
39+
if (nodes != null && sdkField.getParentNodeId() != null) {
40+
sdkField.setParentNode(nodes.get(sdkField.getParentNodeId()));
41+
}
2342
}
2443
}
2544
}

src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNodeRepository.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package eu.europa.ted.eforms.sdk.repository;
22

33
import java.nio.file.Path;
4+
import java.util.ArrayList;
5+
import java.util.List;
46
import com.fasterxml.jackson.databind.JsonNode;
57
import com.fasterxml.jackson.databind.node.ArrayNode;
68
import eu.europa.ted.eforms.sdk.SdkConstants;
@@ -17,9 +19,26 @@ public SdkNodeRepository(String sdkVersion, Path jsonPath) throws InstantiationE
1719
@Override
1820
protected void populateMap(final JsonNode json) throws InstantiationException {
1921
final ArrayNode nodes = (ArrayNode) json.get(SdkConstants.FIELDS_JSON_XML_STRUCTURE_KEY);
22+
List<SdkNode> needsParentWiring = new ArrayList<>();
23+
24+
// First pass: create all nodes, optimistically set parent if already loaded
2025
for (final JsonNode node : nodes) {
2126
final SdkNode sdkNode = SdkEntityFactory.getSdkNode(sdkVersion, node);
2227
put(sdkNode.getId(), sdkNode);
28+
29+
if (sdkNode.getParentId() != null) {
30+
SdkNode parent = get(sdkNode.getParentId());
31+
if (parent != null) {
32+
sdkNode.setParent(parent);
33+
} else {
34+
needsParentWiring.add(sdkNode);
35+
}
36+
}
37+
}
38+
39+
// Second pass: wire up any nodes whose parent wasn't loaded yet
40+
for (SdkNode sdkNode : needsParentWiring) {
41+
sdkNode.setParent(get(sdkNode.getParentId()));
2342
}
2443
}
2544
}

src/test/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactoryTest.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ void testComponentNotFound() throws InstantiationException {
4444

4545
// No component for this type
4646
assertThrows(IllegalArgumentException.class, () ->
47-
factory.getComponentImpl("1.0", SdkComponentType.CODELIST, TestComponent.class));
47+
factory.getComponentImpl("1.0", SdkComponentType.VALIDATOR_GENERATOR, TestComponent.class));
4848

4949
// No component for this qualifier
5050
assertThrows(IllegalArgumentException.class, () ->
@@ -53,9 +53,5 @@ void testComponentNotFound() throws InstantiationException {
5353
// Only component for this version and type does not have a qualifier
5454
assertThrows(IllegalArgumentException.class, () ->
5555
factory.getComponentImpl("0.5", SdkComponentType.EFX_EXPRESSION_TRANSLATOR, "BAD", TestComponent.class));
56-
57-
// Only component for this version and type has a qualifier
58-
assertThrows(IllegalArgumentException.class, () ->
59-
factory.getComponentImpl("1.0", SdkComponentType.NODE, TestComponent.class));
6056
}
6157
}

0 commit comments

Comments
 (0)