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;