From 6fbeccef48ddfe9844ebd47551e36d6848a004b2 Mon Sep 17 00:00:00 2001 From: Bertrand Lorentz Date: Wed, 30 Jul 2025 14:25:06 +0200 Subject: [PATCH 01/24] pom: Set a deployment name when publishing to Maven Central This will make it easier to identify each release on the Central Portal "Deployments" page. Also fix indentation of this section. --- pom.xml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index c4199da..345c066 100644 --- a/pom.xml +++ b/pom.xml @@ -547,15 +547,17 @@ - - org.sonatype.central - central-publishing-maven-plugin - true - - central - true - - + + org.sonatype.central + central-publishing-maven-plugin + true + + central + true + + ${project.artifactId} ${project.version} + + From def9b95014e87558e37ab5c877f5208a6d403e20 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:08:33 +0200 Subject: [PATCH 02/24] Added some code comments to the sdk.component package. --- .../eforms/sdk/component/SdkComponent.java | 5 ++++ .../sdk/component/SdkComponentDescriptor.java | 28 +++++++++++++++---- .../sdk/component/SdkComponentType.java | 3 ++ .../eforms/sdk/component/package-info.java | 8 ++++++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/component/package-info.java diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponent.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponent.java index 8ac3f56..ebc73de 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponent.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponent.java @@ -6,6 +6,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Marks a class as an SDK component implementation for specific SDK versions. + * Each annotated class must correspond to a specific component type and can optionally + * specify a qualifier for multiple implementations of the same type. + */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentDescriptor.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentDescriptor.java index ffb44a4..a2629c3 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentDescriptor.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentDescriptor.java @@ -14,6 +14,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Descriptor that uniquely identifies an SDK component by its version, type, and qualifier. + * Used internally by {@link SdkComponentFactory} for component registry and lookup. + */ public class SdkComponentDescriptor implements Serializable { private static final long serialVersionUID = -6237218459963821365L; @@ -27,11 +31,13 @@ public class SdkComponentDescriptor implements Serializable { private Class implType; - public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType, - Class implType) { - this(sdkVersion, componentType, "", implType); - } - + /** + * Creates a descriptor with the specified SDK version, component type, and qualifier. + * + * @param sdkVersion the SDK version + * @param componentType the component type + * @param qualifier the qualifier (use empty string for default components) + */ public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType, String qualifier, Class implType) { this.sdkVersion = Validate.notBlank(sdkVersion, "Undefined SDK version"); @@ -40,6 +46,18 @@ public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType, this.implType = Validate.notNull(implType, "Undefined implementation type"); } + /** + * Creates a descriptor with the specified SDK version and component type. + * The qualifier defaults to empty string. + * + * @param sdkVersion the SDK version + * @param componentType the component type + */ + public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType, + Class implType) { + this(sdkVersion, componentType, "", implType); + } + @SuppressWarnings("unchecked") public T createInstance(Object... initArgs) throws InstantiationException { try { diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java index a63254d..8d551e7 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java @@ -1,5 +1,8 @@ package eu.europa.ted.eforms.sdk.component; +/** + * Enumeration of component types that can be registered with the SDK component factory. + */ public enum SdkComponentType { FIELD, NODE, CODELIST, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR; } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/package-info.java b/src/main/java/eu/europa/ted/eforms/sdk/component/package-info.java new file mode 100644 index 0000000..1def354 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides an abstract factory pattern implementation for managing SDK version-specific components. + * This package allows multiple implementations of the same component type to coexist, with the + * appropriate implementation selected based on SDK version and optional qualifier. + * + * @since 1.0.0 + */ +package eu.europa.ted.eforms.sdk.component; \ No newline at end of file From a18bf9ae905c67d716ea8bdf8ce63b7d78492812 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 1 Aug 2025 14:09:13 +0200 Subject: [PATCH 03/24] fix: Configure maven-javadoc-plugin to enable doclint with all checks except missing code comments --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 345c066..9e664eb 100644 --- a/pom.xml +++ b/pom.xml @@ -391,6 +391,9 @@ org.apache.maven.plugins maven-javadoc-plugin ${version.javadoc.plugin} + + all,-missing + org.apache.maven.plugins From 5e3f4207bfb9b4bc113c7d5d5b24c9bcec3373d3 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Wed, 12 Nov 2025 02:49:53 +0100 Subject: [PATCH 04/24] Add EFX Rules translator component type - Add EFX_RULES_TRANSLATOR to SdkComponentType enum - Required for Schematron generation from EFX Rules --- .../eu/europa/ted/eforms/sdk/component/SdkComponentType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java index 8d551e7..331ac4f 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java @@ -4,5 +4,5 @@ * Enumeration of component types that can be registered with the SDK component factory. */ public enum SdkComponentType { - FIELD, NODE, CODELIST, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR; + FIELD, NODE, CODELIST, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR; } From c724b0b2c89061e5e5eb626c08efc8b5e257021d Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Tue, 9 Dec 2025 04:48:08 +0100 Subject: [PATCH 05/24] Added SDK notice subtype entity and repository --- .../europa/ted/eforms/sdk/SdkConstants.java | 1 + .../sdk/component/SdkComponentType.java | 2 +- .../eforms/sdk/entity/SdkEntityFactory.java | 6 ++ .../eforms/sdk/entity/SdkNoticeSubtype.java | 70 +++++++++++++++++++ .../repository/SdkNoticeTypeRepository.java | 29 ++++++++ 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java index deeeb97..25ecad1 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java @@ -7,6 +7,7 @@ public class SdkConstants { public static final String FIELDS_JSON_XML_STRUCTURE_KEY = "xmlStructure"; public static final String FIELDS_JSON_FIELDS_KEY = "fields"; + public static final String NOTICE_TYPES_JSON_SUBTYPES_KEY = "noticeSubTypes"; public static final String NOTICE_TYPES_JSON_DOCUMENT_TYPES_KEY = "documentTypes"; public static final String NOTICE_TYPES_JSON_DOCUMENT_TYPE_KEY = "documentType"; public static final String NOTICE_TYPES_JSON_NAMESPACE_KEY = "namespace"; diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java index 331ac4f..9e9b88a 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java @@ -4,5 +4,5 @@ * Enumeration of component types that can be registered with the SDK component factory. */ public enum SdkComponentType { - FIELD, NODE, CODELIST, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR; + FIELD, NODE, CODELIST, NOTICE_TYPE, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR; } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java index 36f9f8b..df91805 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java @@ -30,4 +30,10 @@ public static SdkNode getSdkNode(String sdkVersion, JsonNode node) throws Instan return SdkEntityFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.NODE, SdkNode.class, node); } + + public static SdkNoticeSubtype getSdkNoticeType(final String sdkVersion, final JsonNode json) + throws InstantiationException { + return SdkEntityFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.NOTICE_TYPE, + SdkNoticeSubtype.class, json); + } } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java new file mode 100644 index 0000000..ddf5dd8 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java @@ -0,0 +1,70 @@ +package eu.europa.ted.eforms.sdk.entity; + +import java.util.Objects; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Represents a notice subtype from the SDK's notice-types.json file. + */ +public abstract class SdkNoticeSubtype { + private final String subTypeId; + private final String documentType; + private final String type; + + protected SdkNoticeSubtype(String subTypeId, String documentType, String type) { + this.subTypeId = subTypeId; + this.documentType = documentType; + this.type = type; + } + + protected SdkNoticeSubtype(JsonNode json) { + this.subTypeId = json.get("subTypeId").asText(); + this.documentType = json.get("documentType").asText(); + this.type = json.get("type").asText(); + } + + /** + * Returns the notice subtype ID (e.g., "1", "3", "CEI", "E1", "X01"). + * This is the primary identifier used for phase generation. + */ + public String getId() { + return subTypeId; + } + + public String getSubTypeId() { + return subTypeId; + } + + public String getDocumentType() { + return documentType; + } + + public String getType() { + return type; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SdkNoticeSubtype other = (SdkNoticeSubtype) obj; + return Objects.equals(subTypeId, other.subTypeId); + } + + @Override + public int hashCode() { + return Objects.hash(subTypeId); + } + + @Override + public String toString() { + return subTypeId; + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java new file mode 100644 index 0000000..0a5e76e --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java @@ -0,0 +1,29 @@ +package eu.europa.ted.eforms.sdk.repository; + +import java.nio.file.Path; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import eu.europa.ted.eforms.sdk.SdkConstants; +import eu.europa.ted.eforms.sdk.entity.SdkEntityFactory; +import eu.europa.ted.eforms.sdk.entity.SdkNoticeSubtype; + +/** + * Repository for SDK notice types loaded from notice-types.json. + * Maps notice subtype IDs (e.g., "1", "3", "CEI", "E1", "X01") to SdkNoticeSubtype objects. + */ +public class SdkNoticeTypeRepository extends MapFromJson { + private static final long serialVersionUID = 1L; + + public SdkNoticeTypeRepository(String sdkVersion, Path jsonPath) throws InstantiationException { + super(sdkVersion, jsonPath); + } + + @Override + protected void populateMap(final JsonNode json) throws InstantiationException { + final ArrayNode noticeTypes = (ArrayNode) json.get(SdkConstants.NOTICE_TYPES_JSON_SUBTYPES_KEY); + for (final JsonNode noticeType : noticeTypes) { + final SdkNoticeSubtype sdkNoticeType = SdkEntityFactory.getSdkNoticeType(sdkVersion, noticeType); + put(sdkNoticeType.getId(), sdkNoticeType); + } + } +} From 0331b915fb90d5ec1d47d5111c83218d063680d0 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 26 Dec 2025 12:54:36 +0100 Subject: [PATCH 06/24] Refactor: Rename VALIDATOR_MARKUP_GENERATOR to VALIDATOR_GENERATOR --- .../europa/ted/eforms/sdk/component/SdkComponentType.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java index 9e9b88a..4ed6cad 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java @@ -1,8 +1,10 @@ package eu.europa.ted.eforms.sdk.component; /** - * Enumeration of component types that can be registered with the SDK component factory. + * Enumeration of component types that can be registered with the SDK component + * factory. */ public enum SdkComponentType { - FIELD, NODE, CODELIST, NOTICE_TYPE, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR; + FIELD, NODE, CODELIST, NOTICE_TYPE, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, + SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR, VALIDATOR_GENERATOR; } From 7d77effc1ff09eb6cd388289682ba73319194238 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 26 Dec 2025 14:08:01 +0100 Subject: [PATCH 07/24] Refactor: Update SdkNoticeSubtype and SdkField --- .../eu/europa/ted/eforms/sdk/entity/SdkField.java | 2 +- .../ted/eforms/sdk/entity/SdkNoticeSubtype.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java index 49e8be6..9213798 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java @@ -103,6 +103,6 @@ public int hashCode() { @Override public String toString() { - return "SdkField [id=" + id + "]"; + return id; } } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java index ddf5dd8..0deb351 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java @@ -6,7 +6,7 @@ /** * Represents a notice subtype from the SDK's notice-types.json file. */ -public abstract class SdkNoticeSubtype { +public abstract class SdkNoticeSubtype implements Comparable { private final String subTypeId; private final String documentType; private final String type; @@ -18,9 +18,9 @@ protected SdkNoticeSubtype(String subTypeId, String documentType, String type) { } protected SdkNoticeSubtype(JsonNode json) { - this.subTypeId = json.get("subTypeId").asText(); - this.documentType = json.get("documentType").asText(); - this.type = json.get("type").asText(); + this.subTypeId = json.get("subTypeId").asText(null); + this.documentType = json.get("documentType").asText(null); + this.type = json.get("type").asText(null); } /** @@ -58,6 +58,11 @@ public boolean equals(Object obj) { return Objects.equals(subTypeId, other.subTypeId); } + @Override + public int compareTo(SdkNoticeSubtype o) { + return this.subTypeId.compareTo(o.subTypeId); + } + @Override public int hashCode() { return Objects.hash(subTypeId); From dd51af2acdd573f657861cc84639fafd33598e9a Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 26 Dec 2025 14:39:27 +0100 Subject: [PATCH 08/24] Refactor: Rename getSdkNoticeType to getSdkNoticeSubtype and update variable names --- .../eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java | 2 +- .../eforms/sdk/repository/SdkNoticeTypeRepository.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java index df91805..09705d9 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkEntityFactory.java @@ -31,7 +31,7 @@ public static SdkNode getSdkNode(String sdkVersion, JsonNode node) throws Instan SdkNode.class, node); } - public static SdkNoticeSubtype getSdkNoticeType(final String sdkVersion, final JsonNode json) + public static SdkNoticeSubtype getSdkNoticeSubtype(final String sdkVersion, final JsonNode json) throws InstantiationException { return SdkEntityFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.NOTICE_TYPE, SdkNoticeSubtype.class, json); diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java index 0a5e76e..adfdbba 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNoticeTypeRepository.java @@ -20,10 +20,10 @@ public SdkNoticeTypeRepository(String sdkVersion, Path jsonPath) throws Instanti @Override protected void populateMap(final JsonNode json) throws InstantiationException { - final ArrayNode noticeTypes = (ArrayNode) json.get(SdkConstants.NOTICE_TYPES_JSON_SUBTYPES_KEY); - for (final JsonNode noticeType : noticeTypes) { - final SdkNoticeSubtype sdkNoticeType = SdkEntityFactory.getSdkNoticeType(sdkVersion, noticeType); - put(sdkNoticeType.getId(), sdkNoticeType); + final ArrayNode noticeSubtypes = (ArrayNode) json.get(SdkConstants.NOTICE_TYPES_JSON_SUBTYPES_KEY); + for (final JsonNode noticeSubtype : noticeSubtypes) { + final SdkNoticeSubtype sdkNoticeSubtype = SdkEntityFactory.getSdkNoticeSubtype(sdkVersion, noticeSubtype); + put(sdkNoticeSubtype.getId(), sdkNoticeSubtype); } } } From a73e4fcc1863e6b1dc0a21d6e36f8bb6228c5bac Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:33:04 +0100 Subject: [PATCH 09/24] Add SdkNoticeTypeRepository to README documentation (#41) * Initial plan * Add SdkNoticeTypeRepository reference to README Co-authored-by: rousso <91379+rousso@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rousso <91379+rousso@users.noreply.github.com> --- src/main/java/eu/europa/ted/eforms/sdk/repository/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md b/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md index ac456c0..36e2912 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md @@ -9,4 +9,5 @@ This package contains: * `SdkFieldRepository`: can populate a `HashMap` with `SdkField` objects read form `fields.json` * `SdkNodeRepository`: can populate a `HashMap` with `SdkNode` objects read form `fields.json` -* `SdkCodelistRepository`: can populate a `HashMap` with `SdkCodelist` objects (including all codelist codes), by reading the `.gc` files from the `codelists` folder of the eForms SDK. \ No newline at end of file +* `SdkCodelistRepository`: can populate a `HashMap` with `SdkCodelist` objects (including all codelist codes), by reading the `.gc` files from the `codelists` folder of the eForms SDK. +* `SdkNoticeTypeRepository`: can populate a `HashMap` with `SdkNoticeSubtype` objects read from `notice-types.json` \ No newline at end of file From 2eb247e972b9ab00ef7944f39cb7f3cc0ab7a005 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:35:40 +0100 Subject: [PATCH 10/24] Add SdkNoticeSubtype to entity package README (#42) * Initial plan * Add SdkNoticeSubtype reference to entity README Co-authored-by: rousso <91379+rousso@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rousso <91379+rousso@users.noreply.github.com> --- src/main/java/eu/europa/ted/eforms/sdk/entity/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md b/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md index 1ca638c..a9f0bc6 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md @@ -1,10 +1,11 @@ # Common Entities -The entities in this package can be used while reading data from the eForms SDK. Currently there are only three entities implemented here: +The entities in this package can be used while reading data from the eForms SDK. Currently there are only four entities implemented here: * `SdkField`: Can hold basic information about a field. * `SdkNode`: Can hold basic information about a node and reconstruct the node hierarchy. * `SdkCodelist`: Can hold codelist information including its codes. +* `SdkNoticeSubtype`: Can hold information about a notice subtype from the SDK's notice-types.json file. All the classes are abstract so that they can have specific implementations for different major versions of the eForms SDK if needed. From a58966b1ec39f98701b2c43afa0917cd317e356b Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Mon, 12 Jan 2026 01:48:04 +0100 Subject: [PATCH 11/24] 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 --- .../ted/eforms/sdk/entity/SdkField.java | 36 +++++++++++++++++++ .../europa/ted/eforms/sdk/entity/SdkNode.java | 29 ++++++++++++++- .../eforms/sdk/repository/MapFromJson.java | 29 +++++++++++++-- .../sdk/repository/SdkFieldRepository.java | 19 ++++++++++ .../sdk/repository/SdkNodeRepository.java | 19 ++++++++++ .../component/SdkComponentFactoryTest.java | 6 +--- 6 files changed, 130 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java index 9213798..50aa16c 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java @@ -10,6 +10,8 @@ public abstract class SdkField implements Comparable { private final String parentNodeId; private final String type; private final String codelistId; + private final boolean repeatable; + private SdkNode parentNode; @SuppressWarnings("unused") private SdkField() { @@ -18,12 +20,19 @@ private SdkField() { protected SdkField(final String id, final String type, final String parentNodeId, final String xpathAbsolute, final String xpathRelative, final String codelistId) { + this(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId, false); + } + + protected SdkField(final String id, final String type, final String parentNodeId, + final String xpathAbsolute, final String xpathRelative, final String codelistId, + final boolean repeatable) { this.id = id; this.parentNodeId = parentNodeId; this.xpathAbsolute = xpathAbsolute; this.xpathRelative = xpathRelative; this.type = type; this.codelistId = codelistId; + this.repeatable = repeatable; } protected SdkField(final JsonNode fieldNode) { @@ -33,6 +42,7 @@ protected SdkField(final JsonNode fieldNode) { this.xpathRelative = fieldNode.get("xpathRelative").asText(null); this.type = fieldNode.get("type").asText(null); this.codelistId = extractCodelistId(fieldNode); + this.repeatable = extractRepeatable(fieldNode); } protected String extractCodelistId(final JsonNode fieldNode) { @@ -49,6 +59,20 @@ protected String extractCodelistId(final JsonNode fieldNode) { return valueNode.get("id").asText(null); } + protected boolean extractRepeatable(final JsonNode fieldNode) { + final JsonNode repeatableNode = fieldNode.get("repeatable"); + if (repeatableNode == null) { + return false; + } + + final JsonNode valueNode = repeatableNode.get("value"); + if (valueNode == null) { + return false; + } + + return valueNode.asBoolean(false); + } + public String getId() { return id; } @@ -73,6 +97,18 @@ public String getCodelistId() { return codelistId; } + public boolean isRepeatable() { + return repeatable; + } + + public SdkNode getParentNode() { + return parentNode; + } + + public void setParentNode(SdkNode parentNode) { + this.parentNode = parentNode; + } + /** * Helps with hash maps collisions. Should be consistent with equals. */ diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java index 9166eaf..6cee2d2 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java @@ -1,6 +1,9 @@ package eu.europa.ted.eforms.sdk.entity; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; /** @@ -12,6 +15,8 @@ public abstract class SdkNode implements Comparable { private final String xpathRelative; private final String parentId; private final boolean repeatable; + private SdkNode parent; + private Set cachedAncestry; protected SdkNode(final String id, final String parentId, final String xpathAbsolute, final String xpathRelative, final boolean repeatable) { @@ -51,9 +56,31 @@ public boolean isRepeatable() { return repeatable; } + public SdkNode getParent() { + return parent; + } + + public void setParent(SdkNode parent) { + this.parent = parent; + this.cachedAncestry = null; + } + + public Set getAncestry() { + if (cachedAncestry == null) { + Set ancestry = new LinkedHashSet<>(); + SdkNode current = this; + while (current != null) { + ancestry.add(current.getId()); + current = current.getParent(); + } + cachedAncestry = Collections.unmodifiableSet(ancestry); + } + return cachedAncestry; + } + @Override public int compareTo(SdkNode o) { - return o.getId().compareTo(o.getId()); + return this.getId().compareTo(o.getId()); } @Override diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/MapFromJson.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/MapFromJson.java index 1badd9b..11cdd54 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/MapFromJson.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/MapFromJson.java @@ -39,7 +39,20 @@ protected MapFromJson(final String sdkVersion, final Path jsonPath) } } - private final void populateMap(final Path jsonPath) throws IOException, InstantiationException { + protected MapFromJson(final String sdkVersion, final Path jsonPath, final Object... context) + throws InstantiationException { + this.sdkVersion = sdkVersion; + + try { + populateMap(jsonPath, context); + } catch (IOException e) { + throw new RuntimeException(MessageFormat + .format("Failed to set resource filepath to [{0}]. Error was: {1}", jsonPath, e)); + } + } + + private final void populateMap(final Path jsonPath, final Object... context) + throws IOException, InstantiationException { logger.debug("Populating maps for context, jsonPath={}", jsonPath); final ObjectMapper mapper = buildStandardJacksonObjectMapper(); @@ -54,12 +67,24 @@ private final void populateMap(final Path jsonPath) throws IOException, Instanti } final JsonNode json = mapper.readTree(fieldsJsonInputStream); - populateMap(json); + populateMap(json, context); } } + /** + * Abstract method for populating the map from JSON. Existing subclasses implement this. + */ protected abstract void populateMap(final JsonNode json) throws InstantiationException; + /** + * Context-aware population method. Default implementation delegates to the abstract method, + * ignoring context. Subclasses that need context should override this method. + */ + protected void populateMap(final JsonNode json, final Object... context) + throws InstantiationException { + populateMap(json); + } + /** * @return A reusable Jackson object mapper instance. */ diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java index e901fc9..670bad5 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java @@ -14,12 +14,31 @@ public SdkFieldRepository(String sdkVersion, Path jsonPath) throws Instantiation super(sdkVersion, jsonPath); } + public SdkFieldRepository(String sdkVersion, Path jsonPath, SdkNodeRepository nodeRepository) + throws InstantiationException { + super(sdkVersion, jsonPath, nodeRepository); + } + @Override protected void populateMap(final JsonNode json) throws InstantiationException { + populateMap(json, new Object[0]); + } + + @Override + protected void populateMap(final JsonNode json, final Object... context) + throws InstantiationException { + SdkNodeRepository nodes = (context.length > 0 && context[0] instanceof SdkNodeRepository) + ? (SdkNodeRepository) context[0] + : null; + final ArrayNode fields = (ArrayNode) json.get(SdkConstants.FIELDS_JSON_FIELDS_KEY); for (final JsonNode field : fields) { final SdkField sdkField = SdkEntityFactory.getSdkField(sdkVersion, field); put(sdkField.getId(), sdkField); + + if (nodes != null && sdkField.getParentNodeId() != null) { + sdkField.setParentNode(nodes.get(sdkField.getParentNodeId())); + } } } } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNodeRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNodeRepository.java index adc2e56..c853153 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNodeRepository.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkNodeRepository.java @@ -1,6 +1,8 @@ package eu.europa.ted.eforms.sdk.repository; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import eu.europa.ted.eforms.sdk.SdkConstants; @@ -17,9 +19,26 @@ public SdkNodeRepository(String sdkVersion, Path jsonPath) throws InstantiationE @Override protected void populateMap(final JsonNode json) throws InstantiationException { final ArrayNode nodes = (ArrayNode) json.get(SdkConstants.FIELDS_JSON_XML_STRUCTURE_KEY); + List needsParentWiring = new ArrayList<>(); + + // First pass: create all nodes, optimistically set parent if already loaded for (final JsonNode node : nodes) { final SdkNode sdkNode = SdkEntityFactory.getSdkNode(sdkVersion, node); put(sdkNode.getId(), sdkNode); + + if (sdkNode.getParentId() != null) { + SdkNode parent = get(sdkNode.getParentId()); + if (parent != null) { + sdkNode.setParent(parent); + } else { + needsParentWiring.add(sdkNode); + } + } + } + + // Second pass: wire up any nodes whose parent wasn't loaded yet + for (SdkNode sdkNode : needsParentWiring) { + sdkNode.setParent(get(sdkNode.getParentId())); } } } diff --git a/src/test/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactoryTest.java b/src/test/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactoryTest.java index 4153763..2fca295 100644 --- a/src/test/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactoryTest.java +++ b/src/test/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactoryTest.java @@ -44,7 +44,7 @@ void testComponentNotFound() throws InstantiationException { // No component for this type assertThrows(IllegalArgumentException.class, () -> - factory.getComponentImpl("1.0", SdkComponentType.CODELIST, TestComponent.class)); + factory.getComponentImpl("1.0", SdkComponentType.VALIDATOR_GENERATOR, TestComponent.class)); // No component for this qualifier assertThrows(IllegalArgumentException.class, () -> @@ -53,9 +53,5 @@ void testComponentNotFound() throws InstantiationException { // Only component for this version and type does not have a qualifier assertThrows(IllegalArgumentException.class, () -> factory.getComponentImpl("0.5", SdkComponentType.EFX_EXPRESSION_TRANSLATOR, "BAD", TestComponent.class)); - - // Only component for this version and type has a qualifier - assertThrows(IllegalArgumentException.class, () -> - factory.getComponentImpl("1.0", SdkComponentType.NODE, TestComponent.class)); } } From 57087cac3bb39baeb4a86b9e1d39b4f912835462 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Mon, 12 Jan 2026 01:50:36 +0100 Subject: [PATCH 12/24] TEDEFO-2129: Move versioned SDK entity classes from EXT to ECL --- .../eforms/sdk/entity/v1/SdkCodelistV1.java | 21 ++++++ .../ted/eforms/sdk/entity/v1/SdkFieldV1.java | 65 +++++++++++++++++++ .../ted/eforms/sdk/entity/v1/SdkNodeV1.java | 29 +++++++++ .../sdk/entity/v1/SdkNoticeSubtypeV1.java | 21 ++++++ .../eforms/sdk/entity/v2/SdkCodelistV2.java | 21 ++++++ .../ted/eforms/sdk/entity/v2/SdkFieldV2.java | 43 ++++++++++++ .../ted/eforms/sdk/entity/v2/SdkNodeV2.java | 37 +++++++++++ .../sdk/entity/v2/SdkNoticeSubtypeV2.java | 21 ++++++ 8 files changed, 258 insertions(+) create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkCodelistV1.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkFieldV1.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNodeV1.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNoticeSubtypeV1.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkCodelistV2.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkFieldV2.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNodeV2.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNoticeSubtypeV2.java diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkCodelistV1.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkCodelistV1.java new file mode 100644 index 0000000..d186edc --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkCodelistV1.java @@ -0,0 +1,21 @@ +package eu.europa.ted.eforms.sdk.entity.v1; + +import java.util.List; +import java.util.Optional; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.SdkCodelist; + +/** + * Representation of an SdkCodelist for usage in the symbols map. + * + * @author rouschr + */ +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.CODELIST) +public class SdkCodelistV1 extends SdkCodelist { + + public SdkCodelistV1(final String codelistId, final String codelistVersion, + final List codes, final Optional parentId) { + super(codelistId, codelistVersion, codes, parentId); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkFieldV1.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkFieldV1.java new file mode 100644 index 0000000..0a7457b --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkFieldV1.java @@ -0,0 +1,65 @@ +package eu.europa.ted.eforms.sdk.entity.v1; + +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.SdkField; + +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.FIELD) +public class SdkFieldV1 extends SdkField { + + public SdkFieldV1(final String id, final String type, final String parentNodeId, + final String xpathAbsolute, final String xpathRelative, final String codelistId, + final boolean repeatable) { + super(id, type, parentNodeId, xpathAbsolute, xpathRelative, codelistId, repeatable); + } + + public SdkFieldV1(final JsonNode field) { + super(field); + } + + @JsonCreator + public SdkFieldV1( + @JsonProperty("id") final String id, + @JsonProperty("type") final String type, + @JsonProperty("parentNodeId") final String parentNodeId, + @JsonProperty("xpathAbsolute") final String xpathAbsolute, + @JsonProperty("xpathRelative") final String xpathRelative, + @JsonProperty("codeList") final Map> codelist, + @JsonProperty("repeatable") final Map repeatable) { + this(id, type, parentNodeId, xpathAbsolute, xpathRelative, getCodelistId(codelist), + getRepeatable(repeatable)); + } + + protected static String getCodelistId(Map> codelist) { + if (codelist == null) { + return null; + } + + Map value = codelist.get("value"); + if (value == null) { + return null; + } + + return value.get("id"); + } + + protected static boolean getRepeatable(Map repeatable) { + if (repeatable == null) { + return false; + } + + // If there are constraints, the field may repeat conditionally - treat as repeatable + Object constraints = repeatable.get("constraints"); + if (constraints != null) { + return true; + } + + // Otherwise check the default value + Object value = repeatable.get("value"); + return Boolean.TRUE.equals(value); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNodeV1.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNodeV1.java new file mode 100644 index 0000000..69de45c --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNodeV1.java @@ -0,0 +1,29 @@ +package eu.europa.ted.eforms.sdk.entity.v1; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.SdkNode; + +/** + * A node is something like a section. Nodes can be parents of other nodes or parents of fields. + */ +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.NODE) +public class SdkNodeV1 extends SdkNode { + + @JsonCreator + public SdkNodeV1( + @JsonProperty("id") String id, + @JsonProperty("parentId") String parentId, + @JsonProperty("xpathAbsolute") String xpathAbsolute, + @JsonProperty("xpathRelative") String xpathRelative, + @JsonProperty("repeatable") boolean repeatable) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + } + + public SdkNodeV1(JsonNode node) { + super(node); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNoticeSubtypeV1.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNoticeSubtypeV1.java new file mode 100644 index 0000000..2175b03 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkNoticeSubtypeV1.java @@ -0,0 +1,21 @@ +package eu.europa.ted.eforms.sdk.entity.v1; + +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.SdkNoticeSubtype; + +/** + * Represents a notice subtype from the SDK's notice-types.json file. + */ +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.NOTICE_TYPE) +public class SdkNoticeSubtypeV1 extends SdkNoticeSubtype { + + public SdkNoticeSubtypeV1(String subTypeId, String documentType, String type) { + super(subTypeId, documentType, type); + } + + public SdkNoticeSubtypeV1(JsonNode json) { + super(json); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkCodelistV2.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkCodelistV2.java new file mode 100644 index 0000000..bc4249d --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkCodelistV2.java @@ -0,0 +1,21 @@ +package eu.europa.ted.eforms.sdk.entity.v2; + +import java.util.List; +import java.util.Optional; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.v1.SdkCodelistV1; + +/** + * Representation of an SdkCodelist for usage in the symbols map. + * + * @author rouschr + */ +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.CODELIST) +public class SdkCodelistV2 extends SdkCodelistV1 { + + public SdkCodelistV2(final String codelistId, final String codelistVersion, + final List codes, final Optional parentId) { + super(codelistId, codelistVersion, codes, parentId); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkFieldV2.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkFieldV2.java new file mode 100644 index 0000000..d69ad30 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkFieldV2.java @@ -0,0 +1,43 @@ +package eu.europa.ted.eforms.sdk.entity.v2; + +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.v1.SdkFieldV1; + +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.FIELD) +public class SdkFieldV2 extends SdkFieldV1 { + private final String alias; + + public SdkFieldV2(String id, String type, String parentNodeId, String xpathAbsolute, + String xpathRelative, String rootCodelistId, boolean repeatable, String alias) { + super(id, type, parentNodeId, xpathAbsolute, xpathRelative, rootCodelistId, repeatable); + this.alias = alias; + } + + public SdkFieldV2(JsonNode fieldNode) { + super(fieldNode); + this.alias = fieldNode.has("alias") ? fieldNode.get("alias").asText(null) : null; + } + + @JsonCreator + public SdkFieldV2( + @JsonProperty("id") final String id, + @JsonProperty("type") final String type, + @JsonProperty("parentNodeId") final String parentNodeId, + @JsonProperty("xpathAbsolute") final String xpathAbsolute, + @JsonProperty("xpathRelative") final String xpathRelative, + @JsonProperty("codeList") final Map> codelist, + @JsonProperty("repeatable") final Map repeatable, + @JsonProperty("alias") final String alias) { + this(id, type, parentNodeId, xpathAbsolute, xpathRelative, getCodelistId(codelist), + getRepeatable(repeatable), alias); + } + + public String getAlias() { + return alias; + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNodeV2.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNodeV2.java new file mode 100644 index 0000000..9d98cee --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNodeV2.java @@ -0,0 +1,37 @@ +package eu.europa.ted.eforms.sdk.entity.v2; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.v1.SdkNodeV1; + +/** + * A node is something like a section. Nodes can be parents of other nodes or parents of fields. + */ +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.NODE) +public class SdkNodeV2 extends SdkNodeV1 { + private final String alias; + + @JsonCreator + public SdkNodeV2( + @JsonProperty("id") String id, + @JsonProperty("parentId") String parentId, + @JsonProperty("xpathAbsolute") String xpathAbsolute, + @JsonProperty("xpathRelative") String xpathRelative, + @JsonProperty("repeatable") boolean repeatable, + @JsonProperty("alias") String alias) { + super(id, parentId, xpathAbsolute, xpathRelative, repeatable); + this.alias = alias; + } + + public SdkNodeV2(JsonNode node) { + super(node); + this.alias = node.has("alias") ? node.get("alias").asText(null) : null; + } + + public String getAlias() { + return alias; + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNoticeSubtypeV2.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNoticeSubtypeV2.java new file mode 100644 index 0000000..086a29c --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkNoticeSubtypeV2.java @@ -0,0 +1,21 @@ +package eu.europa.ted.eforms.sdk.entity.v2; + +import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.sdk.component.SdkComponent; +import eu.europa.ted.eforms.sdk.component.SdkComponentType; +import eu.europa.ted.eforms.sdk.entity.v1.SdkNoticeSubtypeV1; + +/** + * Represents a notice subtype from the SDK's notice-types.json file. + */ +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.NOTICE_TYPE) +public class SdkNoticeSubtypeV2 extends SdkNoticeSubtypeV1 { + + public SdkNoticeSubtypeV2(String subTypeId, String documentType, String type) { + super(subTypeId, documentType, type); + } + + public SdkNoticeSubtypeV2(JsonNode json) { + super(json); + } +} From 047396dcbd2dd5028a79e11b10591d6291e75217 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Mon, 12 Jan 2026 09:39:28 +0100 Subject: [PATCH 13/24] TEDEFO-4822 Add getXpathInfo() to SdkField Lazy-initialized XPathInfo provides parsed XPath structure. --- .../eu/europa/ted/eforms/sdk/entity/SdkField.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java index 50aa16c..88c2613 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java @@ -2,6 +2,8 @@ import java.util.Objects; import com.fasterxml.jackson.databind.JsonNode; +import eu.europa.ted.eforms.xpath.XPathInfo; +import eu.europa.ted.eforms.xpath.XPathProcessor; public abstract class SdkField implements Comparable { private final String id; @@ -12,6 +14,7 @@ public abstract class SdkField implements Comparable { private final String codelistId; private final boolean repeatable; private SdkNode parentNode; + private XPathInfo xpathInfo; @SuppressWarnings("unused") private SdkField() { @@ -109,6 +112,18 @@ public void setParentNode(SdkNode parentNode) { this.parentNode = parentNode; } + /** + * Returns parsed XPath information for this field. + * Provides access to attribute info, path decomposition, and predicate checks. + * Lazily initialized on first access. + */ + public XPathInfo getXpathInfo() { + if (this.xpathInfo == null) { + this.xpathInfo = XPathProcessor.parse(this.xpathAbsolute); + } + return this.xpathInfo; + } + /** * Helps with hash maps collisions. Should be consistent with equals. */ From c23a05e502ac76e90417a4381148c9e2e1a34970 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Tue, 3 Feb 2026 15:37:05 +0100 Subject: [PATCH 14/24] Change getAncestry() return type from Set to List for clearer ordering semantics --- .../europa/ted/eforms/sdk/entity/SdkNode.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java index 6cee2d2..1057b4a 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNode.java @@ -1,9 +1,9 @@ package eu.europa.ted.eforms.sdk.entity; +import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; -import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; /** @@ -16,7 +16,7 @@ public abstract class SdkNode implements Comparable { private final String parentId; private final boolean repeatable; private SdkNode parent; - private Set cachedAncestry; + private List cachedAncestry; protected SdkNode(final String id, final String parentId, final String xpathAbsolute, final String xpathRelative, final boolean repeatable) { @@ -60,20 +60,35 @@ public SdkNode getParent() { return parent; } + /** + * Sets the parent node and invalidates the cached ancestry. + * Should only be called during SDK initialization (two-pass loading). + * + * @param parent the parent node + */ public void setParent(SdkNode parent) { this.parent = parent; this.cachedAncestry = null; } - public Set getAncestry() { + /** + * Returns the ancestry chain from this node to the root. + * The list includes this node as the first element, followed by its parent, + * grandparent, and so on up to the root node. + * + * The result is cached and recomputed only when the parent changes. + * + * @return unmodifiable list of node IDs ordered from child (this node) to root + */ + public List getAncestry() { if (cachedAncestry == null) { - Set ancestry = new LinkedHashSet<>(); + List ancestry = new ArrayList<>(); SdkNode current = this; while (current != null) { ancestry.add(current.getId()); current = current.getParent(); } - cachedAncestry = Collections.unmodifiableSet(ancestry); + cachedAncestry = Collections.unmodifiableList(ancestry); } return cachedAncestry; } From 6b02b67fbf9c602dd64bf8b9ce42f848364bb784 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 8 Feb 2026 01:51:42 +0100 Subject: [PATCH 15/24] TEDEFO-4319 Add privacy settings and data types --- .../ted/eforms/sdk/entity/SdkDataType.java | 68 ++++++++++ .../ted/eforms/sdk/entity/SdkField.java | 123 ++++++++++++++++-- .../sdk/repository/SdkDataTypeRepository.java | 60 +++++++++ .../sdk/repository/SdkFieldRepository.java | 23 ++++ 4 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/entity/SdkDataType.java create mode 100644 src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkDataType.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkDataType.java new file mode 100644 index 0000000..396eb57 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkDataType.java @@ -0,0 +1,68 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.eforms.sdk.entity; + +import java.util.Objects; + +/** + * Represents an eForms SDK data type. + * + * Each field in the SDK has a type (e.g., "text", "date", "amount"). This entity captures + * type-level metadata such as the privacy masking value. Currently hardcoded; will be loaded from + * data-types.json when it is added to the SDK. + */ +public class SdkDataType { + private final String id; + private final String privacyMask; + + @SuppressWarnings("unused") + private SdkDataType() { + throw new UnsupportedOperationException(); + } + + public SdkDataType(final String id, final String privacyMask) { + this.id = id; + this.privacyMask = privacyMask; + } + + public String getId() { + return this.id; + } + + public String getPrivacyMask() { + return this.privacyMask; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SdkDataType other = (SdkDataType) obj; + return Objects.equals(this.id, other.id); + } + + @Override + public int hashCode() { + return Objects.hash(this.id); + } + + @Override + public String toString() { + return this.id; + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java index 88c2613..a6fa4c7 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java @@ -13,9 +13,82 @@ public abstract class SdkField implements Comparable { private final String type; private final String codelistId; private final boolean repeatable; + private final String privacyCode; + private final PrivacySettings privacySettings; private SdkNode parentNode; private XPathInfo xpathInfo; + /** + * Privacy settings for fields that can be withheld from publication. + */ + public static class PrivacySettings { + private final String privacyCodeFieldId; + private final String justificationCodeFieldId; + private final String justificationDescriptionFieldId; + private final String publicationDateFieldId; + private SdkField privacyCodeField; + private SdkField justificationCodeField; + private SdkField justificationDescriptionField; + private SdkField publicationDateField; + + public PrivacySettings(final String privacyCodeFieldId, + final String justificationCodeFieldId, final String justificationDescriptionFieldId, + final String publicationDateFieldId) { + this.privacyCodeFieldId = privacyCodeFieldId; + this.justificationCodeFieldId = justificationCodeFieldId; + this.justificationDescriptionFieldId = justificationDescriptionFieldId; + this.publicationDateFieldId = publicationDateFieldId; + } + + public String getPrivacyCodeFieldId() { + return this.privacyCodeFieldId; + } + + public String getJustificationCodeFieldId() { + return this.justificationCodeFieldId; + } + + public String getJustificationDescriptionFieldId() { + return this.justificationDescriptionFieldId; + } + + public String getPublicationDateFieldId() { + return this.publicationDateFieldId; + } + + public SdkField getPrivacyCodeField() { + return this.privacyCodeField; + } + + public void setPrivacyCodeField(SdkField privacyCodeField) { + this.privacyCodeField = privacyCodeField; + } + + public SdkField getJustificationCodeField() { + return this.justificationCodeField; + } + + public void setJustificationCodeField(SdkField justificationCodeField) { + this.justificationCodeField = justificationCodeField; + } + + public SdkField getJustificationDescriptionField() { + return this.justificationDescriptionField; + } + + public void setJustificationDescriptionField(SdkField justificationDescriptionField) { + this.justificationDescriptionField = justificationDescriptionField; + } + + public SdkField getPublicationDateField() { + return this.publicationDateField; + } + + public void setPublicationDateField(SdkField publicationDateField) { + this.publicationDateField = publicationDateField; + } + } + @SuppressWarnings("unused") private SdkField() { throw new UnsupportedOperationException(); @@ -36,6 +109,8 @@ protected SdkField(final String id, final String type, final String parentNodeId this.type = type; this.codelistId = codelistId; this.repeatable = repeatable; + this.privacyCode = null; + this.privacySettings = null; } protected SdkField(final JsonNode fieldNode) { @@ -46,6 +121,9 @@ protected SdkField(final JsonNode fieldNode) { this.type = fieldNode.get("type").asText(null); this.codelistId = extractCodelistId(fieldNode); this.repeatable = extractRepeatable(fieldNode); + final JsonNode privacyNode = fieldNode.get("privacy"); + this.privacyCode = privacyNode != null ? privacyNode.get("code").asText(null) : null; + this.privacySettings = extractPrivacy(privacyNode); } protected String extractCodelistId(final JsonNode fieldNode) { @@ -76,36 +154,59 @@ protected boolean extractRepeatable(final JsonNode fieldNode) { return valueNode.asBoolean(false); } + protected PrivacySettings extractPrivacy(final JsonNode privacyNode) { + if (privacyNode == null) { + return null; + } + + final String privacyCodeFieldId = privacyNode.get("unpublishedFieldId").asText(null); + final String justificationCodeFieldId = privacyNode.get("reasonCodeFieldId").asText(null); + final String justificationDescriptionFieldId = + privacyNode.get("reasonDescriptionFieldId").asText(null); + final String publicationDateFieldId = privacyNode.get("publicationDateFieldId").asText(null); + + return new PrivacySettings(privacyCodeFieldId, justificationCodeFieldId, + justificationDescriptionFieldId, publicationDateFieldId); + } + public String getId() { - return id; + return this.id; } public String getParentNodeId() { - return parentNodeId; + return this.parentNodeId; } public String getXpathAbsolute() { - return xpathAbsolute; + return this.xpathAbsolute; } public String getXpathRelative() { - return xpathRelative; + return this.xpathRelative; } public String getType() { - return type; + return this.type; } public String getCodelistId() { - return codelistId; + return this.codelistId; } public boolean isRepeatable() { - return repeatable; + return this.repeatable; + } + + public String getPrivacyCode() { + return this.privacyCode; + } + + public PrivacySettings getPrivacySettings() { + return this.privacySettings; } public SdkNode getParentNode() { - return parentNode; + return this.parentNode; } public void setParentNode(SdkNode parentNode) { @@ -144,16 +245,16 @@ public boolean equals(Object obj) { return false; } SdkField other = (SdkField) obj; - return Objects.equals(id, other.id); + return Objects.equals(this.id, other.id); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(this.id); } @Override public String toString() { - return id; + return this.id; } } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java new file mode 100644 index 0000000..5f379c4 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java @@ -0,0 +1,60 @@ +/* + * Copyright 2026 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.eforms.sdk.repository; + +import java.util.HashMap; +import eu.europa.ted.eforms.sdk.entity.SdkDataType; + +/** + * Repository of SDK data types. + * + * Currently uses hardcoded type definitions. When data-types.json is added to the SDK, this class + * will be updated to load type metadata from JSON. + */ +public class SdkDataTypeRepository extends HashMap { + private static final long serialVersionUID = 1L; + + /** + * Creates a repository with the default set of SDK data types and their privacy masks. This is a + * temporary approach until data-types.json is available in the SDK. + */ + public static SdkDataTypeRepository createDefault() { + SdkDataTypeRepository repository = new SdkDataTypeRepository(); + + repository.addType("text", "unpublished"); + repository.addType("text-multilingual", "unpublished"); + repository.addType("code", "unpublished"); + repository.addType("internal-code", "unpublished"); + repository.addType("id", "unpublished"); + repository.addType("id-ref", "unpublished"); + repository.addType("phone", "unpublished"); + repository.addType("email", "unpublished"); + repository.addType("url", "unpublished"); + repository.addType("date", "1970-01-01Z"); + repository.addType("zoned-date", "1970-01-01Z"); + repository.addType("time", "00:00:00Z"); + repository.addType("zoned-time", "00:00:00Z"); + repository.addType("indicator", "0"); + repository.addType("integer", "-1"); + repository.addType("number", "-1"); + repository.addType("amount", "-1"); + repository.addType("measure", "-1"); + + return repository; + } + + private void addType(String id, String privacyMask) { + this.put(id, new SdkDataType(id, privacyMask)); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java index 670bad5..6edebfd 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java @@ -32,6 +32,8 @@ protected void populateMap(final JsonNode json, final Object... context) : null; final ArrayNode fields = (ArrayNode) json.get(SdkConstants.FIELDS_JSON_FIELDS_KEY); + + // First pass: create all field entities and add them to the map for (final JsonNode field : fields) { final SdkField sdkField = SdkEntityFactory.getSdkField(sdkVersion, field); put(sdkField.getId(), sdkField); @@ -40,5 +42,26 @@ protected void populateMap(final JsonNode json, final Object... context) sdkField.setParentNode(nodes.get(sdkField.getParentNodeId())); } } + + // Second pass: resolve privacy field references + for (final SdkField sdkField : this.values()) { + if (sdkField.getPrivacySettings() != null) { + SdkField.PrivacySettings privacy = sdkField.getPrivacySettings(); + + if (privacy.getPrivacyCodeFieldId() != null) { + privacy.setPrivacyCodeField(this.get(privacy.getPrivacyCodeFieldId())); + } + if (privacy.getJustificationCodeFieldId() != null) { + privacy.setJustificationCodeField(this.get(privacy.getJustificationCodeFieldId())); + } + if (privacy.getJustificationDescriptionFieldId() != null) { + privacy.setJustificationDescriptionField( + this.get(privacy.getJustificationDescriptionFieldId())); + } + if (privacy.getPublicationDateFieldId() != null) { + privacy.setPublicationDateField(this.get(privacy.getPublicationDateFieldId())); + } + } + } } } From a99c164565b9f52ffb77146e2414c85ddb38befc Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 22 Feb 2026 16:14:27 +0100 Subject: [PATCH 16/24] TEDEFO-4923 Separate duration from measure and improve duration handling --- .../ted/eforms/sdk/entity/SdkField.java | 70 +++++++++++++++++++ .../sdk/repository/SdkDataTypeRepository.java | 1 + .../sdk/repository/SdkFieldRepository.java | 19 ++++- 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java index a6fa4c7..45b67ae 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java @@ -1,5 +1,8 @@ package eu.europa.ted.eforms.sdk.entity; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; import com.fasterxml.jackson.databind.JsonNode; import eu.europa.ted.eforms.xpath.XPathInfo; @@ -15,7 +18,12 @@ public abstract class SdkField implements Comparable { private final boolean repeatable; private final String privacyCode; private final PrivacySettings privacySettings; + private final List attributes; + private final String attributeOf; + private final String attributeName; private SdkNode parentNode; + private List attributeFields; + private SdkField attributeOfField; private XPathInfo xpathInfo; /** @@ -111,6 +119,9 @@ protected SdkField(final String id, final String type, final String parentNodeId this.repeatable = repeatable; this.privacyCode = null; this.privacySettings = null; + this.attributes = Collections.emptyList(); + this.attributeOf = null; + this.attributeName = null; } protected SdkField(final JsonNode fieldNode) { @@ -124,6 +135,21 @@ protected SdkField(final JsonNode fieldNode) { final JsonNode privacyNode = fieldNode.get("privacy"); this.privacyCode = privacyNode != null ? privacyNode.get("code").asText(null) : null; this.privacySettings = extractPrivacy(privacyNode); + this.attributes = extractAttributes(fieldNode); + this.attributeOf = fieldNode.has("attributeOf") ? fieldNode.get("attributeOf").asText(null) : null; + this.attributeName = fieldNode.has("attributeName") ? fieldNode.get("attributeName").asText(null) : null; + } + + protected List extractAttributes(final JsonNode fieldNode) { + final JsonNode attributesNode = fieldNode.get("attributes"); + if (attributesNode == null || !attributesNode.isArray()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (JsonNode attr : attributesNode) { + result.add(attr.asText()); + } + return Collections.unmodifiableList(result); } protected String extractCodelistId(final JsonNode fieldNode) { @@ -193,6 +219,50 @@ public String getCodelistId() { return this.codelistId; } + public List getAttributes() { + return this.attributes; + } + + public String getAttributeOf() { + return this.attributeOf; + } + + public String getAttributeName() { + return this.attributeName; + } + + public List getAttributeFields() { + return this.attributeFields; + } + + public void setAttributeFields(List attributeFields) { + this.attributeFields = Collections.unmodifiableList(attributeFields); + } + + public SdkField getAttributeOfField() { + return this.attributeOfField; + } + + public void setAttributeOfField(SdkField attributeOfField) { + this.attributeOfField = attributeOfField; + } + + /** + * Returns the attribute field with the given XML attribute name (e.g. "unitCode", "listName"), + * or null if this field has no such attribute. + */ + public SdkField getAttributeField(String attrName) { + if (this.attributeFields == null) { + return null; + } + for (SdkField attrField : this.attributeFields) { + if (attrName.equals(attrField.getAttributeName())) { + return attrField; + } + } + return null; + } + public boolean isRepeatable() { return this.repeatable; } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java index 5f379c4..27cd9bd 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepository.java @@ -50,6 +50,7 @@ public static SdkDataTypeRepository createDefault() { repository.addType("number", "-1"); repository.addType("amount", "-1"); repository.addType("measure", "-1"); + repository.addType("duration", "-1"); return repository; } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java index 6edebfd..a06f6aa 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/SdkFieldRepository.java @@ -1,6 +1,8 @@ package eu.europa.ted.eforms.sdk.repository; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import eu.europa.ted.eforms.sdk.SdkConstants; @@ -43,7 +45,7 @@ protected void populateMap(final JsonNode json, final Object... context) } } - // Second pass: resolve privacy field references + // Second pass: resolve cross-field references for (final SdkField sdkField : this.values()) { if (sdkField.getPrivacySettings() != null) { SdkField.PrivacySettings privacy = sdkField.getPrivacySettings(); @@ -62,6 +64,21 @@ protected void populateMap(final JsonNode json, final Object... context) privacy.setPublicationDateField(this.get(privacy.getPublicationDateFieldId())); } } + + if (!sdkField.getAttributes().isEmpty()) { + List attrFields = new ArrayList<>(); + for (String attrFieldId : sdkField.getAttributes()) { + SdkField attrField = this.get(attrFieldId); + if (attrField != null) { + attrFields.add(attrField); + } + } + sdkField.setAttributeFields(attrFields); + } + + if (sdkField.getAttributeOf() != null) { + sdkField.setAttributeOfField(this.get(sdkField.getAttributeOf())); + } } } } From 2c486d624cd25173793cb00a7aa9ceca68e279b9 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Thu, 26 Feb 2026 12:40:17 +0100 Subject: [PATCH 17/24] TEDEFO-4933 Simplify SdkVersion prefix handling and normalizeVersion --- .../eu/europa/ted/eforms/sdk/SdkVersion.java | 6 +++++- .../sdk/component/SdkComponentFactory.java | 19 ++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/SdkVersion.java b/src/main/java/eu/europa/ted/eforms/sdk/SdkVersion.java index d539f00..d7971f4 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkVersion.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkVersion.java @@ -14,11 +14,15 @@ public class SdkVersion implements Comparable { private final Semver version; + private static final String SDK_PREFIX = "eforms-sdk-"; + public SdkVersion(final String version) { Validate.notBlank(version, "Undefined version"); + String normalized = version.startsWith(SDK_PREFIX) ? version.substring(SDK_PREFIX.length()) : version; + // LOOSE because we need to accept MAJOR.MINOR - this.version = new Semver(version, SemverType.LOOSE); + this.version = new Semver(normalized, SemverType.LOOSE); // Check that we did get a MINOR part Validate.notNull(this.version.getMinor()); diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java index 5c91764..80a3482 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java @@ -10,6 +10,7 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; +import eu.europa.ted.eforms.sdk.SdkVersion; import org.reflections.Reflections; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; @@ -174,19 +175,11 @@ protected T getComponentImpl(String sdkVersion, final SdkComponentType compo } private static String normalizeVersion(final String sdkVersion) { - String normalizedVersion = sdkVersion; - - if (normalizedVersion.startsWith("eforms-sdk-")) { - normalizedVersion = normalizedVersion.substring(11); + SdkVersion version = new SdkVersion(sdkVersion); + int major = Integer.parseInt(version.getMajor()); + if (major > 0) { + return version.getMajor(); } - - String[] numbers = normalizedVersion.split("\\.", -2); - - if (numbers.length < 1) { - throw new IllegalArgumentException("Invalid SDK version: " + sdkVersion); - } - - return numbers[0] - + ((numbers.length > 1 && Integer.parseInt(numbers[0]) > 0) ? "" : "." + numbers[1]); + return version.getMajor() + "." + version.getMinor(); } } From 69d7c5d1b92c21f8332793e1d80cb736580541fe Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Fri, 27 Feb 2026 13:28:34 +0100 Subject: [PATCH 18/24] TEDEFO-4939 Fix notice subtype ordering by parsing IDs into prefix/number/suffix --- .../eforms/sdk/entity/SdkNoticeSubtype.java | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java index 0deb351..d7a1708 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkNoticeSubtype.java @@ -1,26 +1,45 @@ package eu.europa.ted.eforms.sdk.entity; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import com.fasterxml.jackson.databind.JsonNode; /** * Represents a notice subtype from the SDK's notice-types.json file. */ public abstract class SdkNoticeSubtype implements Comparable { + private static final Pattern ID_PATTERN = Pattern.compile("^([A-Za-z_-]*)(\\d+)([A-Za-z_-][A-Za-z0-9_-]*)?$"); + private final String subTypeId; private final String documentType; private final String type; + private final String prefix; + private final int number; + private final String suffix; protected SdkNoticeSubtype(String subTypeId, String documentType, String type) { this.subTypeId = subTypeId; this.documentType = documentType; this.type = type; + + Matcher m = ID_PATTERN.matcher(subTypeId != null ? subTypeId : ""); + if (m.matches()) { + this.prefix = m.group(1); + this.number = Integer.parseInt(m.group(2)); + this.suffix = m.group(3) != null ? m.group(3) : ""; + } else { + this.prefix = subTypeId != null ? subTypeId : ""; + this.number = 0; + this.suffix = ""; + } } protected SdkNoticeSubtype(JsonNode json) { - this.subTypeId = json.get("subTypeId").asText(null); - this.documentType = json.get("documentType").asText(null); - this.type = json.get("type").asText(null); + this(json.get("subTypeId").asText(null), + json.get("documentType").asText(null), + json.get("type").asText(null)); } /** @@ -60,7 +79,15 @@ public boolean equals(Object obj) { @Override public int compareTo(SdkNoticeSubtype o) { - return this.subTypeId.compareTo(o.subTypeId); + int cmp = this.prefix.compareTo(o.prefix); + if (cmp != 0) { + return cmp; + } + cmp = Integer.compare(this.number, o.number); + if (cmp != 0) { + return cmp; + } + return this.suffix.compareTo(o.suffix); } @Override From a15bbed3efcb63b4b6f7fc43915141cd9ca2b8ef Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Sun, 22 Mar 2026 20:37:02 +0100 Subject: [PATCH 19/24] TEDEFO-4992 Add NoticeDocument and SafeDocumentBuilder from eforms-notice-viewer --- README.md | 2 + .../eu/europa/ted/eforms/NoticeDocument.java | 176 ++++++++++++++++++ .../europa/ted/util/SafeDocumentBuilder.java | 96 ++++++++++ 3 files changed, 274 insertions(+) create mode 100644 src/main/java/eu/europa/ted/eforms/NoticeDocument.java create mode 100644 src/main/java/eu/europa/ted/util/SafeDocumentBuilder.java diff --git a/README.md b/README.md index 822ec49..0396b9f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ This library provides a set of classes that can be used to solve some common "pr * Automatically discovering and downloading new versions of the eForms SDK. * Maintaining and instantiating at runtime the correct application component versions for different major versions of the SDK. * Basic parsing and processing of XPath expressions. +* Parsing eForms notice XML documents and extracting metadata (SDK version, subtype, languages). +* Secure XML document building with XXE prevention (OWASP guidelines). ## Using the eForms Core Library diff --git a/src/main/java/eu/europa/ted/eforms/NoticeDocument.java b/src/main/java/eu/europa/ted/eforms/NoticeDocument.java new file mode 100644 index 0000000..c534856 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/NoticeDocument.java @@ -0,0 +1,176 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.eforms; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathNodes; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import eu.europa.ted.util.SafeDocumentBuilder; + +/** + * A class representing a Notice document with accessor methods for its XML contents and metadata. + */ +public class NoticeDocument { + + private static final String TAG_PRIMARY_LANGUAGE = "cbc:NoticeLanguageCode"; + private static final String TAG_SDK_VERSION = "cbc:CustomizationID"; + private static final String TAG_SUBTYPE_CODE = "cbc:SubTypeCode"; + private static final String XPATH_ADDITIONAL_LANGUAGE = + "/*/AdditionalNoticeLanguage/ID/text()"; + + private static final XPath xpath = XPathFactory.newInstance().newXPath(); + + private final Element root; + private final String xmlContents; + + public NoticeDocument(final Path noticeXmlPath) + throws ParserConfigurationException, SAXException, IOException { + Validate.notNull(noticeXmlPath, "Undefined Notice XML file path"); + + if (!Files.isRegularFile(noticeXmlPath)) { + throw new FileNotFoundException(noticeXmlPath.toString()); + } + + this.xmlContents = Files.readString(noticeXmlPath, StandardCharsets.UTF_8); + this.root = parseXmlRoot(this.xmlContents); + } + + public NoticeDocument(final InputStream noticeXmlInput) + throws ParserConfigurationException, SAXException, IOException { + Validate.notNull(noticeXmlInput, "Undefined Notice XML input"); + + this.xmlContents = new String(noticeXmlInput.readAllBytes(), StandardCharsets.UTF_8); + this.root = parseXmlRoot(this.xmlContents); + } + + public NoticeDocument(final String noticeXmlContents) + throws ParserConfigurationException, SAXException, IOException { + Validate.notBlank(noticeXmlContents, "Invalid Notice XML contents"); + + this.xmlContents = noticeXmlContents; + this.root = parseXmlRoot(this.xmlContents); + } + + private static Element parseXmlRoot(final String xmlContents) + throws ParserConfigurationException, SAXException, IOException { + try (InputStream input = + new java.io.ByteArrayInputStream(xmlContents.getBytes(StandardCharsets.UTF_8))) { + final Element root = + SafeDocumentBuilder.buildSafeDocumentBuilderAllowDoctype().parse(input) + .getDocumentElement(); + Validate.notNull(root, "No XML root found"); + return root; + } + } + + /** + * Gets the notice sub type from the notice XML. + * + * @return The notice sub type as found in the notice XML + */ + public String getNoticeSubType() { + return Optional.ofNullable(this.root.getElementsByTagName(TAG_SUBTYPE_CODE)) + .map((final NodeList subTypeCodes) -> { + Optional result = Optional.empty(); + for (int i = 0; i < subTypeCodes.getLength(); i++) { + result = Optional.ofNullable(subTypeCodes.item(i)) + .filter((final Node node) -> node.getAttributes() != null) + .map(Node::getTextContent) + .map(StringUtils::strip); + } + return result.orElse(null); + }) + .filter(StringUtils::isNotBlank) + .orElseThrow(() -> new RuntimeException("SubTypeCode not found in notice XML")); + } + + /** + * Gets the eForms SDK version from the notice XML. + * + * @return The eForms SDK version as found in the notice XML + */ + public String getEformsSdkVersion() { + return Optional.ofNullable(this.root.getElementsByTagName(TAG_SDK_VERSION)) + .filter((final NodeList nodes) -> nodes.getLength() == 1) + .map((final NodeList nodes) -> Optional.ofNullable(nodes.item(0)) + .map(Node::getTextContent) + .map(StringUtils::strip) + .map((final String str) -> str.startsWith("eforms-sdk-") + ? str.substring("eforms-sdk-".length()) : str) + .orElse(null)) + .filter(StringUtils::isNotBlank) + .orElseThrow(() -> new RuntimeException("eForms SDK version not found in notice XML")); + } + + /** + * Gets the primary language from the notice XML. + * + * @return The primary language + */ + public String getPrimaryLanguage() { + return Optional + .ofNullable(this.root.getElementsByTagName(TAG_PRIMARY_LANGUAGE)) + .map((final NodeList nodes) -> nodes.item(0)) + .map(Node::getTextContent) + .orElse(null); + } + + /** + * Gets the list of other languages from the notice XML. + * + * @return A list of other languages + * @throws XPathExpressionException If an error occurs evaluating the XPath expression + */ + public List getOtherLanguages() throws XPathExpressionException { + return Optional + .ofNullable(xpath.evaluateExpression(XPATH_ADDITIONAL_LANGUAGE, + this.root.getOwnerDocument(), XPathNodes.class)) + .map((final XPathNodes nodes) -> { + final List languages = new ArrayList<>(); + nodes.forEach((final Node node) -> { + if (StringUtils.isNotBlank(node.getTextContent())) { + languages.add(node.getTextContent()); + } + }); + return languages; + }) + .orElseGet(ArrayList::new); + } + + /** + * Gets the notice XML contents. + * + * @return The notice XML + */ + public String getXmlContents() { + return this.xmlContents; + } +} diff --git a/src/main/java/eu/europa/ted/util/SafeDocumentBuilder.java b/src/main/java/eu/europa/ted/util/SafeDocumentBuilder.java new file mode 100644 index 0000000..b70d385 --- /dev/null +++ b/src/main/java/eu/europa/ted/util/SafeDocumentBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2022 European Union + * + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European + * Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in + * compliance with the Licence. You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under the Licence + * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the Licence for the specific language governing permissions and limitations under + * the Licence. + */ +package eu.europa.ted.util; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for the creation of {@link DocumentBuilder} instances for XML parsing, using XXE + * prevention techniques as recommended by OWASP. + * + * @see OWASP + * XXE Prevention Cheat Sheet + */ +public class SafeDocumentBuilder { + + private static final Logger logger = LoggerFactory.getLogger(SafeDocumentBuilder.class); + + private SafeDocumentBuilder() { + throw new AssertionError("Utility class."); + } + + /** + * Creates a {@link DocumentBuilder} using XXE prevention techniques. Allows DOCTYPE declarations. + * + * @return A {@link DocumentBuilder} instance + * @throws ParserConfigurationException when the builder is configured with a feature that is + * unsupported by the XML processor + */ + public static DocumentBuilder buildSafeDocumentBuilderAllowDoctype() + throws ParserConfigurationException { + return buildSafeDocumentBuilder(false); + } + + /** + * Creates a {@link DocumentBuilder} using XXE prevention techniques. Raises a fatal error when a + * DOCTYPE declaration is found. + * + * @return A {@link DocumentBuilder} instance + * @throws ParserConfigurationException when the builder is configured with a feature that is + * unsupported by the XML processor + */ + public static DocumentBuilder buildSafeDocumentBuilderStrict() + throws ParserConfigurationException { + return buildSafeDocumentBuilder(true); + } + + private static DocumentBuilder buildSafeDocumentBuilder(final boolean disallowDoctypeDecl) + throws ParserConfigurationException { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance(); + String feature = null; + try { + feature = "http://apache.org/xml/features/disallow-doctype-decl"; + dbf.setFeature(feature, disallowDoctypeDecl); + + feature = "http://xml.org/sax/features/external-general-entities"; + dbf.setFeature(feature, false); + + feature = "http://xml.org/sax/features/external-parameter-entities"; + dbf.setFeature(feature, false); + + feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + dbf.setFeature(feature, false); + + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setValidating(false); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); + + return dbf.newDocumentBuilder(); + } catch (final ParserConfigurationException e) { + logger.info("Error: The feature '{}' is probably not supported by your XML processor.", + feature); + logger.debug("ParserConfigurationException was thrown:", e); + throw e; + } + } +} From 34c4cca6499100d72d015a5a2bb122b43c430368 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Wed, 25 Mar 2026 02:40:27 +0100 Subject: [PATCH 20/24] TEDEFO-5002 Add SdkComponentType entries for dependency extractors --- .../eu/europa/ted/eforms/sdk/component/SdkComponentType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java index 4ed6cad..a81fbce 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentType.java @@ -6,5 +6,6 @@ */ public enum SdkComponentType { FIELD, NODE, CODELIST, NOTICE_TYPE, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, + EFX_COMPUTE_DEPENDENCY_EXTRACTOR, EFX_VALIDATION_DEPENDENCY_EXTRACTOR, SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR, VALIDATOR_GENERATOR; } From 9da568414409115cfda544b400b574621f3e0d1d Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Thu, 26 Mar 2026 12:36:21 +0100 Subject: [PATCH 21/24] docs: Update changelog for 1.6.0 release --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4632fe..9d68945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,40 @@ -# eForms Core Library 1.5.0 Release Notes +# eForms Core Library 1.6.0 Release Notes -The eForms Core Library is a collection of utilities that are used by our sample applications as well as the EFX Toolkit for Java Developers. +The eForms Core Library is a collection of utilities used by the EFX Toolkit for Java Developers and other eForms applications. ## In this release -This release fixes an issue in the XPathProcessor that could cause a redundant predicate production when contextualising XPaths with multiple predicates. +### SDK entity improvements -The versions of various dependencies was updated: Apache Commons IO 2.19.0, Apache Commons Lang 3.18.0, Jackson 2.18.3, logback 1.5.18. +- Versioned SDK entity classes (`SdkFieldV1`, `SdkFieldV2`, `SdkNodeV1`, `SdkNodeV2`, etc.) have been moved from the EFX Toolkit into the core library, consolidating version-specific implementations in a single location. +- `SdkNode` now supports parent node references and ancestor chain traversal via `getAncestry()`. The return type is `List` (ordered) rather than `Set`. +- `SdkField` now exposes repeatability information, parent node references, and parsed XPath metadata via `getXpathInfo()`. +- Repository classes (`SdkNodeRepository`, `SdkFieldRepository`) now use two-pass loading to wire parent-child relationships during initialization. + +### Privacy and data type support + +- Added `PrivacySettings` to `SdkField`, providing access to privacy code, justification, publication date, and related field references. +- Introduced `SdkDataType` entity and `SdkDataTypeRepository` for field type-level metadata including privacy masking values. +- Separated `duration` as a distinct data type from `measure`. + +### Notice subtype management + +- Added `SdkNoticeSubtype` entity with intelligent ID parsing (prefix/number/suffix decomposition) and correct sorting order. +- Added `SdkNoticeTypeRepository` to load and manage notice subtypes. +- Renamed `getSdkNoticeType()` to `getSdkNoticeSubtype()` for semantic accuracy. + +### Utilities + +- Moved `NoticeDocument` and `SafeDocumentBuilder` from the eforms-notice-viewer into the core library. `NoticeDocument` provides secure XML parsing with accessors for notice subtype, SDK version, and language detection. `SafeDocumentBuilder` implements XXE prevention following OWASP guidelines. + +### Component registry + +- Added component types for dependency extraction (`EFX_COMPUTE_DEPENDENCY_EXTRACTOR`, `EFX_VALIDATION_DEPENDENCY_EXTRACTOR`) and EFX rules translation (`EFX_RULES_TRANSLATOR`). +- Renamed `VALIDATOR_MARKUP_GENERATOR` to `VALIDATOR_GENERATOR`. + +### Dependencies + +- Updated versions of various dependencies. ## Download From 41cc66ee63c77635e92b793a74e9e689e024c0f1 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Thu, 26 Mar 2026 12:40:01 +0100 Subject: [PATCH 22/24] docs: Update package READMEs for 1.6.0 --- src/main/java/eu/europa/ted/eforms/sdk/README.md | 4 +++- src/main/java/eu/europa/ted/eforms/sdk/entity/README.md | 8 ++++---- .../java/eu/europa/ted/eforms/sdk/repository/README.md | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/eu/europa/ted/eforms/sdk/README.md b/src/main/java/eu/europa/ted/eforms/sdk/README.md index 39fdd00..32102b1 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/README.md +++ b/src/main/java/eu/europa/ted/eforms/sdk/README.md @@ -4,6 +4,8 @@ The `eu.europa.ted.eforms.sdk` package contains the core classes and packages th The main packages included here are: -* `component`: Provides a solution for handling multiple major versions of the SDK in parallel. +* `component`: Provides a solution for handling multiple major versions of the SDK in parallel. +* `entity`: Provides abstract entity classes for representing SDK metadata (fields, nodes, codelists, notice subtypes, data types). +* `repository`: Provides classes for reading SDK entities from JSON and Genericode files. * `resource`: Provides a solution for automatically discovering and downloading new versions of the eForms SDK. diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md b/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md index a9f0bc6..ff413cf 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/README.md @@ -1,14 +1,14 @@ # Common Entities -The entities in this package can be used while reading data from the eForms SDK. Currently there are only four entities implemented here: +The entities in this package can be used while reading data from the eForms SDK. The following entities are implemented here: -* `SdkField`: Can hold basic information about a field. -* `SdkNode`: Can hold basic information about a node and reconstruct the node hierarchy. +* `SdkField`: Can hold basic information about a field, including repeatability, parent node, XPath metadata, and privacy settings. +* `SdkNode`: Can hold basic information about a node and reconstruct the node hierarchy via parent references and ancestor chain traversal. * `SdkCodelist`: Can hold codelist information including its codes. * `SdkNoticeSubtype`: Can hold information about a notice subtype from the SDK's notice-types.json file. +* `SdkDataType`: Can hold field type-level metadata including privacy masking values. All the classes are abstract so that they can have specific implementations for different major versions of the eForms SDK if needed. This package also includes a factory class (`SdkEntityFactory`) that is meant to be used for instantiating concrete implementations of these abstract entity classes for different major versions of the eForms SDK. -_There is no rocket science in the code in this package. You are welcome to reuse it. It is intended to be used primarily by the EFX Toolkit and our sample applications._ diff --git a/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md b/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md index 36e2912..598c7be 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md +++ b/src/main/java/eu/europa/ted/eforms/sdk/repository/README.md @@ -10,4 +10,5 @@ This package contains: * `SdkFieldRepository`: can populate a `HashMap` with `SdkField` objects read form `fields.json` * `SdkNodeRepository`: can populate a `HashMap` with `SdkNode` objects read form `fields.json` * `SdkCodelistRepository`: can populate a `HashMap` with `SdkCodelist` objects (including all codelist codes), by reading the `.gc` files from the `codelists` folder of the eForms SDK. -* `SdkNoticeTypeRepository`: can populate a `HashMap` with `SdkNoticeSubtype` objects read from `notice-types.json` \ No newline at end of file +* `SdkNoticeTypeRepository`: can populate a `HashMap` with `SdkNoticeSubtype` objects read from `notice-types.json` +* `SdkDataTypeRepository`: can populate a `HashMap` with `SdkDataType` objects \ No newline at end of file From dd3a3c6f3d73bb82fd7a61feae87965196b8d6c7 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Thu, 26 Mar 2026 12:48:05 +0100 Subject: [PATCH 23/24] docs: Fix component type rename description in changelog --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d68945..a5eabc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ The eForms Core Library is a collection of utilities used by the EFX Toolkit for ### SDK entity improvements - Versioned SDK entity classes (`SdkFieldV1`, `SdkFieldV2`, `SdkNodeV1`, `SdkNodeV2`, etc.) have been moved from the EFX Toolkit into the core library, consolidating version-specific implementations in a single location. -- `SdkNode` now supports parent node references and ancestor chain traversal via `getAncestry()`. The return type is `List` (ordered) rather than `Set`. +- `SdkNode` now supports parent node references and ancestor chain traversal via `getAncestry()`. - `SdkField` now exposes repeatability information, parent node references, and parsed XPath metadata via `getXpathInfo()`. - Repository classes (`SdkNodeRepository`, `SdkFieldRepository`) now use two-pass loading to wire parent-child relationships during initialization. @@ -21,7 +21,6 @@ The eForms Core Library is a collection of utilities used by the EFX Toolkit for - Added `SdkNoticeSubtype` entity with intelligent ID parsing (prefix/number/suffix decomposition) and correct sorting order. - Added `SdkNoticeTypeRepository` to load and manage notice subtypes. -- Renamed `getSdkNoticeType()` to `getSdkNoticeSubtype()` for semantic accuracy. ### Utilities @@ -30,7 +29,6 @@ The eForms Core Library is a collection of utilities used by the EFX Toolkit for ### Component registry - Added component types for dependency extraction (`EFX_COMPUTE_DEPENDENCY_EXTRACTOR`, `EFX_VALIDATION_DEPENDENCY_EXTRACTOR`) and EFX rules translation (`EFX_RULES_TRANSLATOR`). -- Renamed `VALIDATOR_MARKUP_GENERATOR` to `VALIDATOR_GENERATOR`. ### Dependencies From 2b6928168d90e7b36cdc1309809b110515220557 Mon Sep 17 00:00:00 2001 From: Ioannis Rosuochatzakis Date: Thu, 26 Mar 2026 13:43:12 +0100 Subject: [PATCH 24/24] pom: Set version to 1.6.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9e664eb..90bb10f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ eu.europa.ted.eforms eforms-core-java - 1.6.0-SNAPSHOT + 1.6.0 eForms Core Library API and tools for eForms applications.