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 d3c9d77..df3d88c 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java @@ -17,6 +17,7 @@ public class SdkConstants { public static final String NODES_JSON_FILE_NAME = "nodes.json"; public static final String FIELDS_JSON_FILE_NAME = "fields.json"; public static final String DATA_TYPES_JSON_FILE_NAME = "data-types.json"; + public static final String DATA_TYPES_JSON_DATA_TYPES_KEY = "dataTypes"; public static final String BUSINESS_TERMS_JSON_FILE_NAME = "business-terms.json"; public static final String BUSINESS_ENTITIES_PROPERTIES_JSON_FILE_NAME = "business-entities-properties.json"; public static final String BUSINESS_ENTITIES_HIERARCHIES_JSON_FILE_NAME = "business-entities-hierarchies.json"; 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 a81fbce..d6f9a08 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 @@ -5,7 +5,7 @@ * factory. */ public enum SdkComponentType { - FIELD, NODE, CODELIST, NOTICE_TYPE, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR, + FIELD, NODE, CODELIST, NOTICE_TYPE, DATA_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; } 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 index 396eb57..5bac603 100644 --- a/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkDataType.java +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/SdkDataType.java @@ -14,17 +14,23 @@ package eu.europa.ted.eforms.sdk.entity; import java.util.Objects; +import com.fasterxml.jackson.databind.JsonNode; /** * 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. + * type-level metadata such as the privacy masking value, the attribute that the fields of the type + * carry (e.g., the currency of an amount) with its data type, and the code list of the data types + * whose values come from one. 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; + private final String attributeName; + private final String attributeType; + private final String listName; @SuppressWarnings("unused") private SdkDataType() { @@ -34,6 +40,27 @@ private SdkDataType() { public SdkDataType(final String id, final String privacyMask) { this.id = id; this.privacyMask = privacyMask; + this.attributeName = null; + this.attributeType = null; + this.listName = null; + } + + /** + * Creates a data type from its entry in data-types.json. + */ + protected SdkDataType(final JsonNode dataType) { + this.id = dataType.get("type").asText(null); + this.privacyMask = + dataType.hasNonNull("maskingValue") ? dataType.get("maskingValue").asText(null) : null; + final JsonNode attribute = dataType.get("attribute"); + this.attributeName = attribute != null && attribute.hasNonNull("name") + ? attribute.get("name").asText(null) + : null; + this.attributeType = attribute != null && attribute.hasNonNull("type") + ? attribute.get("type").asText(null) + : null; + this.listName = + dataType.hasNonNull("listName") ? dataType.get("listName").asText(null) : null; } public String getId() { @@ -44,6 +71,30 @@ public String getPrivacyMask() { return this.privacyMask; } + /** + * Returns the name of the attribute that the fields of this data type carry (e.g., "currencyID" + * for an amount), or null if they carry none. + */ + public String getAttributeName() { + return this.attributeName; + } + + /** + * Returns the data type of the attribute that the fields of this data type carry (e.g., "currency" + * for an amount), or null if they carry none. + */ + public String getAttributeType() { + return this.attributeType; + } + + /** + * Returns the name of the root code list that the values of this data type come from (e.g., + * "timeperiod" for a duration unit), or null if they come from none. + */ + public String getListName() { + return this.listName; + } + @Override public boolean equals(Object obj) { if (this == obj) { 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 09705d9..e49e5d6 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 @@ -36,4 +36,10 @@ public static SdkNoticeSubtype getSdkNoticeSubtype(final String sdkVersion, fina return SdkEntityFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.NOTICE_TYPE, SdkNoticeSubtype.class, json); } + + public static SdkDataType getSdkDataType(final String sdkVersion, final JsonNode dataType) + throws InstantiationException { + return SdkEntityFactory.INSTANCE.getComponentImpl(sdkVersion, SdkComponentType.DATA_TYPE, + SdkDataType.class, dataType); + } } diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkDataTypeV1.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkDataTypeV1.java new file mode 100644 index 0000000..6f42fc7 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v1/SdkDataTypeV1.java @@ -0,0 +1,27 @@ +/* + * 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.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.SdkDataType; + +@SdkComponent(versions = {"1"}, componentType = SdkComponentType.DATA_TYPE) +public class SdkDataTypeV1 extends SdkDataType { + + public SdkDataTypeV1(final JsonNode dataType) { + super(dataType); + } +} diff --git a/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkDataTypeV2.java b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkDataTypeV2.java new file mode 100644 index 0000000..52e7758 --- /dev/null +++ b/src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkDataTypeV2.java @@ -0,0 +1,27 @@ +/* + * 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.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.SdkDataTypeV1; + +@SdkComponent(versions = {"2"}, componentType = SdkComponentType.DATA_TYPE) +public class SdkDataTypeV2 extends SdkDataTypeV1 { + + public SdkDataTypeV2(final JsonNode dataType) { + super(dataType); + } +} 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 27cd9bd..3a7220c 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 @@ -13,22 +13,46 @@ */ package eu.europa.ted.eforms.sdk.repository; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.util.HashMap; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.europa.ted.eforms.sdk.SdkConstants; import eu.europa.ted.eforms.sdk.entity.SdkDataType; +import eu.europa.ted.eforms.sdk.entity.SdkEntityFactory; /** * 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. + * Currently uses hardcoded type definitions, in the data-types.json resource of this library. */ public class SdkDataTypeRepository extends HashMap { private static final long serialVersionUID = 1L; + public SdkDataTypeRepository() { + super(); + } + + /** + * Creates the repository of the data types of the given SDK version, with their privacy masks and + * the attributes that their fields carry. They are read from the data-types.json resource of + * this library, which has the format of fields/fwd/data-types.json in SDK 2. SDK 1 has no such + * file, so this is where its data types come from. + */ + public SdkDataTypeRepository(final String sdkVersion) throws InstantiationException { + this.populateMap(sdkVersion, readResource()); + } + /** * 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. + * + * @deprecated Use {@link #SdkDataTypeRepository(String)}, which creates the data types of the + * given SDK version, with the attributes that their fields carry. */ + @Deprecated public static SdkDataTypeRepository createDefault() { SdkDataTypeRepository repository = new SdkDataTypeRepository(); @@ -58,4 +82,21 @@ public static SdkDataTypeRepository createDefault() { private void addType(String id, String privacyMask) { this.put(id, new SdkDataType(id, privacyMask)); } + + private void populateMap(final String sdkVersion, final JsonNode json) + throws InstantiationException { + for (final JsonNode dataType : json.get(SdkConstants.DATA_TYPES_JSON_DATA_TYPES_KEY)) { + final SdkDataType sdkDataType = SdkEntityFactory.getSdkDataType(sdkVersion, dataType); + this.put(sdkDataType.getId(), sdkDataType); + } + } + + private static JsonNode readResource() { + try (InputStream input = + SdkDataTypeRepository.class.getResourceAsStream(SdkConstants.DATA_TYPES_JSON_FILE_NAME)) { + return new ObjectMapper().readTree(input); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/src/main/resources/eu/europa/ted/eforms/sdk/repository/data-types.json b/src/main/resources/eu/europa/ted/eforms/sdk/repository/data-types.json new file mode 100644 index 0000000..1e97dfc --- /dev/null +++ b/src/main/resources/eu/europa/ted/eforms/sdk/repository/data-types.json @@ -0,0 +1,132 @@ +{ + "dataTypes": [ + { + "type": "amount", + "maskingValue": "-1", + "attribute": { + "name": "currencyID", + "type": "currency" + } + }, + { + "type": "code", + "maskingValue": "unpublished", + "attribute": { + "name": "listName", + "type": "text" + } + }, + { + "type": "currency", + "maskingValue": "unpublished", + "listName": "currency" + }, + { + "type": "date", + "maskingValue": "1970-01-01Z" + }, + { + "type": "duration", + "maskingValue": "-1", + "attribute": { + "name": "unitCode", + "type": "duration-unit" + } + }, + { + "type": "duration-unit", + "maskingValue": "unpublished", + "listName": "timeperiod" + }, + { + "type": "email", + "maskingValue": "unpublished" + }, + { + "type": "id", + "maskingValue": "unpublished", + "attribute": { + "name": "schemeName", + "type": "text" + } + }, + { + "type": "id-ref", + "maskingValue": "unpublished", + "attribute": { + "name": "schemeName", + "type": "text" + } + }, + { + "type": "indicator", + "maskingValue": "0" + }, + { + "type": "integer", + "maskingValue": "-1" + }, + { + "type": "internal-code", + "maskingValue": "unpublished", + "attribute": { + "name": "listName", + "type": "text" + } + }, + { + "type": "language", + "maskingValue": "unpublished", + "listName": "language" + }, + { + "type": "measure", + "maskingValue": "-1", + "attribute": { + "name": "unitCode", + "type": "measurement-unit" + } + }, + { + "type": "measurement-unit", + "maskingValue": "unpublished", + "listName": "measurement-unit" + }, + { + "type": "number", + "maskingValue": "-1" + }, + { + "type": "phone", + "maskingValue": "unpublished" + }, + { + "type": "text", + "maskingValue": "unpublished" + }, + { + "type": "text-multilingual", + "maskingValue": "unpublished", + "attribute": { + "name": "languageID", + "type": "language" + } + }, + { + "type": "time", + "maskingValue": "00:00:00Z" + }, + { + "type": "url", + "maskingValue": "unpublished" + }, + { + "type": "zoned-date", + "maskingValue": "1970-01-01Z" + }, + { + "type": "zoned-time", + "maskingValue": "00:00:00Z" + } + ] +} diff --git a/src/test/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepositoryTest.java b/src/test/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepositoryTest.java new file mode 100644 index 0000000..c17d753 --- /dev/null +++ b/src/test/java/eu/europa/ted/eforms/sdk/repository/SdkDataTypeRepositoryTest.java @@ -0,0 +1,97 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; +import eu.europa.ted.eforms.sdk.entity.SdkDataType; +import eu.europa.ted.eforms.sdk.entity.v1.SdkDataTypeV1; +import eu.europa.ted.eforms.sdk.entity.v2.SdkDataTypeV2; + +class SdkDataTypeRepositoryTest { + + @Test + void testSdk1DataTypes() throws InstantiationException { + assertEquals(SdkDataTypeV1.class, new SdkDataTypeRepository("1.10.0").get("amount").getClass()); + } + + @Test + void testSdk2DataTypes() throws InstantiationException { + assertEquals(SdkDataTypeV2.class, + new SdkDataTypeRepository("eforms-sdk-2.0").get("amount").getClass()); + } + + @Test + void testAllDataTypesAreLoaded() throws InstantiationException { + assertEquals(23, new SdkDataTypeRepository("2.0.0").size()); + } + + @Test + void testAmountHasCurrencyAttribute() throws InstantiationException { + SdkDataType amount = new SdkDataTypeRepository("2.0.0").get("amount"); + + assertEquals("-1", amount.getPrivacyMask()); + assertEquals("currencyID", amount.getAttributeName()); + assertEquals("currency", amount.getAttributeType()); + } + + @Test + void testDurationAndMeasureHaveTheirOwnUnits() throws InstantiationException { + SdkDataTypeRepository repository = new SdkDataTypeRepository("2.0.0"); + + assertEquals("duration-unit", repository.get("duration").getAttributeType()); + assertEquals("measurement-unit", repository.get("measure").getAttributeType()); + } + + @Test + void testCodelistOfTheUnitsOfDuration() throws InstantiationException { + SdkDataTypeRepository repository = new SdkDataTypeRepository("2.0.0"); + + assertEquals("timeperiod", + repository.get(repository.get("duration").getAttributeType()).getListName()); + } + + @Test + void testTypesWithRootCodelist() throws InstantiationException { + SdkDataTypeRepository repository = new SdkDataTypeRepository("2.0.0"); + + assertEquals("currency", repository.get("currency").getListName()); + assertEquals("timeperiod", repository.get("duration-unit").getListName()); + assertEquals("language", repository.get("language").getListName()); + assertEquals("measurement-unit", repository.get("measurement-unit").getListName()); + assertNull(repository.get("currency").getAttributeName()); + } + + @Test + void testAttributeOfTypeText() throws InstantiationException { + SdkDataTypeRepository repository = new SdkDataTypeRepository("2.0.0"); + + assertEquals("listName", repository.get("code").getAttributeName()); + assertEquals("text", repository.get("code").getAttributeType()); + assertNull(repository.get("code").getListName()); + assertEquals("schemeName", repository.get("id").getAttributeName()); + assertEquals("text", repository.get("id").getAttributeType()); + } + + @Test + void testTypeWithoutAttribute() throws InstantiationException { + SdkDataType date = new SdkDataTypeRepository("2.0.0").get("date"); + + assertEquals("1970-01-01Z", date.getPrivacyMask()); + assertNull(date.getAttributeName()); + assertNull(date.getAttributeType()); + assertNull(date.getListName()); + } +}