Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
894a474
Merge branch 'main' into develop and bump version to 1.5.0-SNAPSHOT
bertrand-lorentz Jul 30, 2025
6fbecce
pom: Set a deployment name when publishing to Maven Central
bertrand-lorentz Jul 30, 2025
def9b95
Added some code comments to the sdk.component package.
rousso Aug 1, 2025
a18bf9a
fix: Configure maven-javadoc-plugin to enable doclint with all checks…
rousso Aug 1, 2025
5e3f420
Add EFX Rules translator component type
rousso Nov 12, 2025
c724b0b
Added SDK notice subtype entity and repository
rousso Dec 9, 2025
0331b91
Refactor: Rename VALIDATOR_MARKUP_GENERATOR to VALIDATOR_GENERATOR
rousso Dec 26, 2025
7d77eff
Refactor: Update SdkNoticeSubtype and SdkField
rousso Dec 26, 2025
dd51af2
Refactor: Rename getSdkNoticeType to getSdkNoticeSubtype and update v…
rousso Dec 26, 2025
a73e4fc
Add SdkNoticeTypeRepository to README documentation (#41)
Copilot Jan 6, 2026
2eb247e
Add SdkNoticeSubtype to entity package README (#42)
Copilot Jan 6, 2026
a58966b
TEDEFO-4821: Add hierarchy traversal and repeatability to SDK entities
rousso Jan 12, 2026
57087ca
TEDEFO-2129: Move versioned SDK entity classes from EXT to ECL
rousso Jan 12, 2026
047396d
TEDEFO-4822 Add getXpathInfo() to SdkField
rousso Jan 12, 2026
722b77d
Merge branch 'TEDEFO-4805-efx-rules-translator' into TEDEFO-4806-tigh…
rousso Feb 2, 2026
860c50e
Merge pull request #40 from OP-TED/TEDEFO-4805-efx-rules-translator
bertrand-lorentz Feb 2, 2026
c23a05e
Change getAncestry() return type from Set to List for clearer orderin…
rousso Feb 3, 2026
53fc630
Merge pull request #43 from OP-TED/TEDEFO-4806-tighter-type-checking
bertrand-lorentz Feb 3, 2026
6b02b67
TEDEFO-4319 Add privacy settings and data types
rousso Feb 8, 2026
3462dc2
Merge pull request #44 from OP-TED/TEDEFO-4319-privacy-settings-in-efx
bertrand-lorentz Feb 9, 2026
a99c164
TEDEFO-4923 Separate duration from measure and improve duration handling
rousso Feb 22, 2026
8f4b6e8
Merge pull request #45 from OP-TED/TEDEFO-4923-duration-measure-split
bertrand-lorentz Feb 23, 2026
2c486d6
TEDEFO-4933 Simplify SdkVersion prefix handling and normalizeVersion
rousso Feb 26, 2026
e06d095
Merge pull request #46 from OP-TED/TEDEFO-4933-efx-autocomplete
bertrand-lorentz Feb 26, 2026
69d7c5d
TEDEFO-4939 Fix notice subtype ordering by parsing IDs into prefix/nu…
rousso Feb 27, 2026
658368c
Merge pull request #47 from OP-TED/bugfix/TEDEFO-4939-notice-subtype-…
bertrand-lorentz Feb 27, 2026
a15bbed
TEDEFO-4992 Add NoticeDocument and SafeDocumentBuilder from eforms-no…
rousso Mar 22, 2026
1dc77f0
Merge pull request #48 from OP-TED/TEDEFO-4991-consolidate-library-de…
bertrand-lorentz Mar 23, 2026
34c4cca
TEDEFO-5002 Add SdkComponentType entries for dependency extractors
rousso Mar 25, 2026
c2a07ba
Merge pull request #49 from OP-TED/TEDEFO-5001-dependency-graph
bertrand-lorentz Mar 25, 2026
9da5684
docs: Update changelog for 1.6.0 release
rousso Mar 26, 2026
41cc66e
docs: Update package READMEs for 1.6.0
rousso Mar 26, 2026
dd3a3c6
docs: Fix component type rename description in changelog
rousso Mar 26, 2026
2b69281
pom: Set version to 1.6.0
rousso Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
# 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()`.
- `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.

### 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.

## Download

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 16 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>eu.europa.ted.eforms</groupId>
<artifactId>eforms-core-java</artifactId>
<version>1.5.0</version>
<version>1.6.0</version>

<name>eForms Core Library</name>
<description>API and tools for eForms applications.</description>
Expand Down Expand Up @@ -33,7 +33,7 @@
</scm>

<properties>
<project.build.outputTimestamp>2024-08-02T09:50:45Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2025-07-30T08:40:55Z</project.build.outputTimestamp>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!-- Java compiler -->
Expand Down Expand Up @@ -391,6 +391,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${version.javadoc.plugin}</version>
<configuration>
<doclint>all,-missing</doclint>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down Expand Up @@ -547,15 +550,17 @@
</gpgArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
<!-- Name displayed on the Central Portal "Deployments" page -->
<deploymentName>${project.artifactId} ${project.version}</deploymentName>
</configuration>
</plugin>
</plugins>
</build>
</profile>
Expand Down
176 changes: 176 additions & 0 deletions src/main/java/eu/europa/ted/eforms/NoticeDocument.java
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> getOtherLanguages() throws XPathExpressionException {
return Optional
.ofNullable(xpath.evaluateExpression(XPATH_ADDITIONAL_LANGUAGE,
this.root.getOwnerDocument(), XPathNodes.class))
.map((final XPathNodes nodes) -> {
final List<String> 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;
}
}
4 changes: 3 additions & 1 deletion src/main/java/eu/europa/ted/eforms/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

1 change: 1 addition & 0 deletions src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/eu/europa/ted/eforms/sdk/SdkVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ public class SdkVersion implements Comparable<SdkVersion> {

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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> implements Serializable {
private static final long serialVersionUID = -6237218459963821365L;

Expand All @@ -27,11 +31,13 @@ public class SdkComponentDescriptor<T> implements Serializable {

private Class<T> implType;

public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType,
Class<T> 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<T> implType) {
this.sdkVersion = Validate.notBlank(sdkVersion, "Undefined SDK version");
Expand All @@ -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<T> implType) {
this(sdkVersion, componentType, "", implType);
}

@SuppressWarnings("unchecked")
public T createInstance(Object... initArgs) throws InstantiationException {
try {
Expand Down
Loading
Loading