diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5eabc2..73e65f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,38 +1,20 @@
-# eForms Core Library 1.6.0 Release Notes
+# eForms Core Library 1.7.0 Release Notes
The eForms Core Library is a collection of utilities used by the EFX Toolkit for Java Developers and other eForms applications.
## In this release
-### SDK entity improvements
+### SDK constants and resources
-- 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()`.
-- `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.
+- Added new SDK path, filename, and resource constants, including support for the validation folder and the `fwd` forward folder used by SDK 2.
-### Privacy and data type support
+### Performance
-- 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`.
+- Replaced Reflections with ClassGraph for component scanning and share a single classpath scan across factories, reducing startup cost (`SdkComponentFactory`).
-### Notice subtype management
+### Fixes
-- Added `SdkNoticeSubtype` entity with intelligent ID parsing (prefix/number/suffix decomposition) and correct sorting order.
-- Added `SdkNoticeTypeRepository` to load and manage notice subtypes.
-
-### 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`).
-
-### Dependencies
-
-- Updated versions of various dependencies.
+- `SdkFieldV1` now maps the `measure` type to `duration`, consistent with the `duration`/`measure` split introduced in 1.6.0.
## Download
diff --git a/pom.xml b/pom.xml
index 90bb10f..435c7e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
eu.europa.ted.eforms
eforms-core-java
- 1.6.0
+ 1.7.0
eForms Core Library
API and tools for eForms applications.
@@ -33,7 +33,7 @@
- 2025-07-30T08:40:55Z
+ 2026-07-17T04:40:22Z
UTF-8
@@ -54,7 +54,7 @@
3.8.6
11.1.3
7.1.1
- 0.10.2
+ 4.8.179
1.8.2
3.1.0
2.0.3
@@ -76,9 +76,9 @@
- org.reflections
- reflections
- ${version.reflections}
+ io.github.classgraph
+ classgraph
+ ${version.classgraph}
@@ -228,8 +228,8 @@
- org.reflections
- reflections
+ io.github.classgraph
+ classgraph
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 25ecad1..d3c9d77 100644
--- a/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java
+++ b/src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java
@@ -4,9 +4,29 @@
import eu.europa.ted.eforms.sdk.resource.PathResource;
public class SdkConstants {
+
+ public static final Path DEFAULT_SDK_ROOT = Path.of("eforms-sdk");
+
+ public static final String CODELISTS_DIR_NAME = "codelists";
+ public static final String CODELISTS_JSON_FILE_NAME = "codelists.json";
+
public static final String FIELDS_JSON_XML_STRUCTURE_KEY = "xmlStructure";
public static final String FIELDS_JSON_FIELDS_KEY = "fields";
+ public static final String FIELDS_DIR_NAME = "fields";
+ 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 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";
+ public static final String BUSINESS_ENTITIES_COMPOSITION_JSON_FILE_NAME = "business-entities-composition.json";
+
+ public static final String MIGRATION_DIR_NAME = "migration";
+ public static final String MIGRATION_JSON_FILE_NAME = "migration.json";
+
+ public static final String NOTICE_TYPES_DIR_NAME = "notice-types";
+ public static final String NOTICE_TYPES_JSON_FILE_NAME = "notice-types.json";
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";
@@ -14,56 +34,110 @@ public class SdkConstants {
public static final String NOTICE_TYPES_JSON_ROOT_ELEMENT_KEY = "rootElement";
public static final String NOTICE_CUSTOMIZATION_ID_VERSION_PREFIX = "eforms-sdk-";
- public static final Path DEFAULT_SDK_ROOT = Path.of("eforms-sdk");
-
public static final String SDK_GROUP_ID = "eu.europa.ted.eforms";
public static final String SDK_ARTIFACT_ID = "eforms-sdk";
public static final String SDK_PACKAGING = "jar";
+ public static final String SCHEMAS_DIR_NAME = "schemas";
+ public static final String SCHEMAS_JSON_FILE_NAME = "schemas.json";
+ public static final String SCHEMATRONS_DIR_NAME = "schematrons";
+
+ public static final String TRANSLATIONS_DIR_NAME = "translations";
+ public static final String TRANSLATIONS_JSON_FILE_NAME = "translations.json";
+ public static final String VALIDATION_DIR_NAME = "validation";
+ public static final String VALIDATION_RULES_EFX_FILE_NAME = "rules.efx";
+ public static final String VALIDATION_DEPENDENCIES_JSON_FILE_NAME = "dependencies.json";
+
+ public static final String VIEW_TEMPLATES_DIR_NAME = "view-templates";
+ public static final String VIEW_TEMPLATES_JSON_FILE_NAME = "view-templates.json";
+
+ /**
+ * Forward folder of SDK2+, it can exist in some folders.
+ * Files in that folder can be used to preview SDK2 features.
+ */
+ public static final String FWD_DIR_NAME = "fwd";
+
private SdkConstants() {}
public enum SdkResource implements PathResource {
- CODELISTS(Path.of("codelists")), //
- CODELISTS_JSON(Path.of("codelists", "codelists.json")), //
+ CODELISTS(Path.of(CODELISTS_DIR_NAME)),
+ CODELISTS_JSON(Path.of(CODELISTS_DIR_NAME, CODELISTS_JSON_FILE_NAME)),
+
+ EFX_GRAMMAR(Path.of("efx-grammar")),
+
+ FIELDS(Path.of(FIELDS_DIR_NAME)),
+ FIELDS_FWD(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME)),
+
+ /**
+ * Fields and nodes.
+ */
+ FIELDS_JSON(Path.of(FIELDS_DIR_NAME, FIELDS_JSON_FILE_NAME)),
- EFX_GRAMMAR(Path.of("efx-grammar")), //
+ FIELDS_FWD_BUSINESS_ENTITIES_COMPOSITION(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, BUSINESS_ENTITIES_COMPOSITION_JSON_FILE_NAME)),
+ FIELDS_FWD_BUSINESS_ENTITIES_HIERARCHIES(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, BUSINESS_ENTITIES_HIERARCHIES_JSON_FILE_NAME)),
+ FIELDS_FWD_BUSINESS_ENTITIES_PROPERTIES(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, BUSINESS_ENTITIES_PROPERTIES_JSON_FILE_NAME)),
+ FIELDS_FWD_BUSINESS_TERMS(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, BUSINESS_TERMS_JSON_FILE_NAME)),
+ FIELDS_FWD_DATA_TYPES(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, DATA_TYPES_JSON_FILE_NAME)),
- FIELDS(Path.of("fields")), //
- FIELDS_JSON(Path.of("fields", "fields.json")), //
+ /**
+ * JSON with array of fields (forward folder).
+ */
+ FIELDS_FWD_FIELDS_JSON(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, FIELDS_JSON_FILE_NAME)),
- NOTICE_TYPES(Path.of("notice-types")), //
- NOTICE_TYPES_JSON(Path.of("notice-types", "notice-types.json")), //
+ /**
+ * JSON with array of nodes (forward folder).
+ */
+ FIELDS_FWD_NODES_JSON(Path.of(FIELDS_DIR_NAME, FWD_DIR_NAME, NODES_JSON_FILE_NAME)),
/**
- * XSD files.
+ * Related to asset migration.
*/
- SCHEMAS(Path.of("schemas")), //
- SCHEMAS_COMMON(Path.of("schemas", "common")), //
- SCHEMAS_MAINDOC(Path.of("schemas", "maindoc")), //
+ MIGRATION(Path.of(MIGRATION_DIR_NAME)),
+ MIGRATION_JSON(Path.of(MIGRATION_DIR_NAME, MIGRATION_JSON_FILE_NAME)),
- SCHEMATRONS(Path.of("schematrons")), //
- SCHEMATRONS_DYNAMIC(Path.of("schematrons", "dynamic")), //
- SCHEMATRONS_STATIC(Path.of("schematrons", "static")), //
+ /**
+ * Notice Types Definitions (NTD).
+ */
+ NOTICE_TYPES(Path.of(NOTICE_TYPES_DIR_NAME)),
+ NOTICE_TYPES_JSON(Path.of(NOTICE_TYPES_DIR_NAME, NOTICE_TYPES_JSON_FILE_NAME)),
+
+ /**
+ * Schema files.
+ */
+ SCHEMAS(Path.of(SCHEMAS_DIR_NAME)),
+ SCHEMAS_JSON(Path.of(SCHEMAS_DIR_NAME, SCHEMAS_JSON_FILE_NAME)),
+
+ SCHEMAS_COMMON(Path.of(SCHEMAS_DIR_NAME, "common")),
+ SCHEMAS_MAINDOC(Path.of(SCHEMAS_DIR_NAME, "maindoc")),
+
+ SCHEMATRONS(Path.of(SCHEMATRONS_DIR_NAME)),
+ SCHEMATRONS_DYNAMIC(Path.of(SCHEMATRONS_DIR_NAME, "dynamic")),
+ SCHEMATRONS_STATIC(Path.of(SCHEMATRONS_DIR_NAME, "static")),
/**
* Internal usage, tedweb.
*/
- TED(Path.of(".ted")), //
- TED_TEDWEB(Path.of(".ted", "tedweb")), //
- TED_TEDWEB_REPORT_METADATA(Path.of(".ted", "tedweb", "report-metadata.json")), //
- TED_TEDWEB_SEARCH_METADATA(Path.of(".ted", "tedweb", "search-metadata.json")), //
+ TED(Path.of(".ted")),
+ TED_TEDWEB(Path.of(".ted", "tedweb")),
+ TED_TEDWEB_REPORT_METADATA(Path.of(".ted", "tedweb", "report-metadata.json")),
+ TED_TEDWEB_SEARCH_METADATA(Path.of(".ted", "tedweb", "search-metadata.json")),
/**
* Internationalisation, labels.
*/
- TRANSLATIONS(Path.of("translations")), //
+ TRANSLATIONS(Path.of(TRANSLATIONS_DIR_NAME)),
+
/**
* The index file for translations, only present in SDK 1.10.0 and later.
*/
- TRANSLATIONS_JSON(Path.of("translations", "translations.json")), //
+ TRANSLATIONS_JSON(Path.of(TRANSLATIONS_DIR_NAME, TRANSLATIONS_JSON_FILE_NAME)),
+
+ VALIDATION(Path.of(VALIDATION_DIR_NAME)),
+ VALIDATION_RULES_EFX(Path.of(VALIDATION_DIR_NAME, VALIDATION_RULES_EFX_FILE_NAME)),
+ VALIDATION_DEPENDENCIES_JSON(Path.of(VALIDATION_DIR_NAME, VALIDATION_DEPENDENCIES_JSON_FILE_NAME)),
- VIEW_TEMPLATES(Path.of("view-templates")), //
- VIEW_TEMPLATES_JSON(Path.of("view-templates", "view-templates.json"));
+ VIEW_TEMPLATES(Path.of(VIEW_TEMPLATES_DIR_NAME)),
+ VIEW_TEMPLATES_JSON(Path.of(VIEW_TEMPLATES_DIR_NAME, VIEW_TEMPLATES_JSON_FILE_NAME));
private Path path;
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 80a3482..2cc9f5e 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
@@ -9,11 +9,9 @@
import java.util.Map.Entry;
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;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ScanResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,6 +21,26 @@
public abstract class SdkComponentFactory {
private static final Logger logger = LoggerFactory.getLogger(SdkComponentFactory.class);
+ /**
+ * Lazy-init holder for the JVM-wide classpath scan. Triggered the first time any
+ * {@link SdkComponentFactory} subclass is constructed; the resulting list is shared
+ * by all subclasses so that the (potentially expensive) classpath walk runs only once
+ * per JVM rather than once per factory subclass.
+ */
+ private static final class AnnotatedClassesHolder {
+ static final List> CLASSES = scan();
+
+ private static List> scan() {
+ logger.debug("Scanning the classpath for types annotated with {}", SdkComponent.class);
+ try (ScanResult result = new ClassGraph()
+ .enableAnnotationInfo()
+ .ignoreClassVisibility()
+ .scan()) {
+ return result.getClassesWithAnnotation(SdkComponent.class).loadClasses();
+ }
+ }
+ }
+
private Map>> componentsMap;
class ComponentSelector {
@@ -65,67 +83,47 @@ protected SdkComponentFactory() {
private void populateComponents() {
Class annotationType = SdkComponent.class;
- logger.debug("Looking in the classpath for types annotated with {}", annotationType);
-
if (componentsMap == null) {
componentsMap = new HashMap<>();
}
- // Get a list of all the packages loaded by the available classloaders.
- // This can be a bit expensive in some situations, so this method factory should
- // be ensured to run as less as possible (ideally only once).
- String[] availablePackages = Arrays
- .stream(ClasspathHelper.classLoaders())
- .map(ClassLoader::getDefinedPackages)
- .flatMap(Stream::of)
- .map(Package::getName)
- .toArray(String[]::new);
+ AnnotatedClassesHolder.CLASSES.forEach((Class> clazz) -> {
+ logger.trace("Processing type [{}]", clazz);
- if (logger.isTraceEnabled()) {
- final List packages = Arrays.asList(availablePackages);
- packages.stream().sorted()
- .forEach(p -> logger.trace(p));
- }
+ SdkComponent annotation = clazz.getAnnotation(annotationType);
+
+ String[] supportedSdkVersions = annotation.versions();
+ SdkComponentType componentType = annotation.componentType();
+ String qualifier = annotation.qualifier();
+ ComponentSelector selector = new ComponentSelector(componentType, qualifier);
+
+ logger.trace("Class [{}] has a component type of [{}] and supports SDK versions [{}]",
+ clazz, componentType, supportedSdkVersions);
+
+ Arrays.asList(supportedSdkVersions).forEach((String sdkVersion) -> {
+ SdkComponentDescriptor> component =
+ new SdkComponentDescriptor<>(sdkVersion, componentType, clazz);
+
+ Map> components =
+ componentsMap.get(sdkVersion);
+
+ if (components != null) {
+ SdkComponentDescriptor> existingComponent = components.get(selector);
+
+ if (existingComponent != null && !existingComponent.equals(component)) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ "More than one components of type [{0}] have been found for SDK version [{1}]:\n\t- {2}\n\t- {3}",
+ componentType, sdkVersion, existingComponent.getImplType().getName(),
+ clazz.getName()));
+ }
+ } else {
+ components = new HashMap<>();
+ componentsMap.put(sdkVersion, components);
+ }
- new Reflections(ConfigurationBuilder.build().forPackages(availablePackages))
- .getTypesAnnotatedWith(annotationType).stream()
- .forEach((Class> clazz) -> {
- logger.trace("Processing type [{}]", clazz);
-
- SdkComponent annotation = clazz.getAnnotation(annotationType);
-
- String[] supportedSdkVersions = annotation.versions();
- SdkComponentType componentType = annotation.componentType();
- String qualifier = annotation.qualifier();
- ComponentSelector selector = new ComponentSelector(componentType, qualifier);
-
- logger.trace("Class [{}] has a component type of [{}] and supports SDK versions [{}]",
- clazz, componentType, supportedSdkVersions);
-
- Arrays.asList(supportedSdkVersions).forEach((String sdkVersion) -> {
- SdkComponentDescriptor> component =
- new SdkComponentDescriptor<>(sdkVersion, componentType, clazz);
-
- Map> components =
- componentsMap.get(sdkVersion);
-
- if (components != null) {
- SdkComponentDescriptor> existingComponent = components.get(selector);
-
- if (existingComponent != null && !existingComponent.equals(component)) {
- throw new IllegalArgumentException(MessageFormat.format(
- "More than one components of type [{0}] have been found for SDK version [{1}]:\n\t- {2}\n\t- {3}",
- componentType, sdkVersion, existingComponent.getImplType().getName(),
- clazz.getName()));
- }
- } else {
- components = new HashMap<>();
- componentsMap.put(sdkVersion, components);
- }
-
- components.put(selector, component);
- });
- });
+ components.put(selector, component);
+ });
+ });
}
protected T getComponentImpl(String sdkVersion, final SdkComponentType componentType,
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
index 0a7457b..940454a 100644
--- 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
@@ -34,6 +34,16 @@ public SdkFieldV1(
getRepeatable(repeatable));
}
+ // In SDK 1.x, "measure" semantically means "duration" (there was no "duration" type yet).
+ @Override
+ public String getType() {
+ String type = super.getType();
+ if ("measure".equals(type)) {
+ return "duration";
+ }
+ return type;
+ }
+
protected static String getCodelistId(Map> codelist) {
if (codelist == null) {
return null;