Skip to content

Commit 88b401e

Browse files
Merge pull request #51 from OP-TED/release/1.6.0
Release/1.6.0
2 parents fd44222 + 2b69281 commit 88b401e

34 files changed

+1290
-58
lines changed

CHANGELOG.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
1-
# eForms Core Library 1.5.0 Release Notes
1+
# eForms Core Library 1.6.0 Release Notes
22

3-
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.
3+
The eForms Core Library is a collection of utilities used by the EFX Toolkit for Java Developers and other eForms applications.
44

55
## In this release
66

7-
This release fixes an issue in the XPathProcessor that could cause a redundant predicate production when contextualising XPaths with multiple predicates.
7+
### SDK entity improvements
88

9-
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.
9+
- 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.
10+
- `SdkNode` now supports parent node references and ancestor chain traversal via `getAncestry()`.
11+
- `SdkField` now exposes repeatability information, parent node references, and parsed XPath metadata via `getXpathInfo()`.
12+
- Repository classes (`SdkNodeRepository`, `SdkFieldRepository`) now use two-pass loading to wire parent-child relationships during initialization.
13+
14+
### Privacy and data type support
15+
16+
- Added `PrivacySettings` to `SdkField`, providing access to privacy code, justification, publication date, and related field references.
17+
- Introduced `SdkDataType` entity and `SdkDataTypeRepository` for field type-level metadata including privacy masking values.
18+
- Separated `duration` as a distinct data type from `measure`.
19+
20+
### Notice subtype management
21+
22+
- Added `SdkNoticeSubtype` entity with intelligent ID parsing (prefix/number/suffix decomposition) and correct sorting order.
23+
- Added `SdkNoticeTypeRepository` to load and manage notice subtypes.
24+
25+
### Utilities
26+
27+
- 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.
28+
29+
### Component registry
30+
31+
- Added component types for dependency extraction (`EFX_COMPUTE_DEPENDENCY_EXTRACTOR`, `EFX_VALIDATION_DEPENDENCY_EXTRACTOR`) and EFX rules translation (`EFX_RULES_TRANSLATOR`).
32+
33+
### Dependencies
34+
35+
- Updated versions of various dependencies.
1036

1137
## Download
1238

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ This library provides a set of classes that can be used to solve some common "pr
77
* Automatically discovering and downloading new versions of the eForms SDK.
88
* Maintaining and instantiating at runtime the correct application component versions for different major versions of the SDK.
99
* Basic parsing and processing of XPath expressions.
10+
* Parsing eForms notice XML documents and extracting metadata (SDK version, subtype, languages).
11+
* Secure XML document building with XXE prevention (OWASP guidelines).
1012

1113
## Using the eForms Core Library
1214

pom.xml

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>eu.europa.ted.eforms</groupId>
55
<artifactId>eforms-core-java</artifactId>
6-
<version>1.5.0</version>
6+
<version>1.6.0</version>
77

88
<name>eForms Core Library</name>
99
<description>API and tools for eForms applications.</description>
@@ -33,7 +33,7 @@
3333
</scm>
3434

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

3939
<!-- Java compiler -->
@@ -391,6 +391,9 @@
391391
<groupId>org.apache.maven.plugins</groupId>
392392
<artifactId>maven-javadoc-plugin</artifactId>
393393
<version>${version.javadoc.plugin}</version>
394+
<configuration>
395+
<doclint>all,-missing</doclint>
396+
</configuration>
394397
</plugin>
395398
<plugin>
396399
<groupId>org.apache.maven.plugins</groupId>
@@ -547,15 +550,17 @@
547550
</gpgArguments>
548551
</configuration>
549552
</plugin>
550-
<plugin>
551-
<groupId>org.sonatype.central</groupId>
552-
<artifactId>central-publishing-maven-plugin</artifactId>
553-
<extensions>true</extensions>
554-
<configuration>
555-
<publishingServerId>central</publishingServerId>
556-
<autoPublish>true</autoPublish>
557-
</configuration>
558-
</plugin>
553+
<plugin>
554+
<groupId>org.sonatype.central</groupId>
555+
<artifactId>central-publishing-maven-plugin</artifactId>
556+
<extensions>true</extensions>
557+
<configuration>
558+
<publishingServerId>central</publishingServerId>
559+
<autoPublish>true</autoPublish>
560+
<!-- Name displayed on the Central Portal "Deployments" page -->
561+
<deploymentName>${project.artifactId} ${project.version}</deploymentName>
562+
</configuration>
563+
</plugin>
559564
</plugins>
560565
</build>
561566
</profile>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright 2022 European Union
3+
*
4+
* Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European
5+
* Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in
6+
* compliance with the Licence. You may obtain a copy of the Licence at:
7+
* https://joinup.ec.europa.eu/software/page/eupl
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the Licence
10+
* is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the Licence for the specific language governing permissions and limitations under
12+
* the Licence.
13+
*/
14+
package eu.europa.ted.eforms;
15+
16+
import java.io.FileNotFoundException;
17+
import java.io.IOException;
18+
import java.io.InputStream;
19+
import java.nio.charset.StandardCharsets;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Optional;
25+
import javax.xml.parsers.ParserConfigurationException;
26+
import javax.xml.xpath.XPath;
27+
import javax.xml.xpath.XPathExpressionException;
28+
import javax.xml.xpath.XPathFactory;
29+
import javax.xml.xpath.XPathNodes;
30+
import org.apache.commons.lang3.StringUtils;
31+
import org.apache.commons.lang3.Validate;
32+
import org.w3c.dom.Element;
33+
import org.w3c.dom.Node;
34+
import org.w3c.dom.NodeList;
35+
import org.xml.sax.SAXException;
36+
import eu.europa.ted.util.SafeDocumentBuilder;
37+
38+
/**
39+
* A class representing a Notice document with accessor methods for its XML contents and metadata.
40+
*/
41+
public class NoticeDocument {
42+
43+
private static final String TAG_PRIMARY_LANGUAGE = "cbc:NoticeLanguageCode";
44+
private static final String TAG_SDK_VERSION = "cbc:CustomizationID";
45+
private static final String TAG_SUBTYPE_CODE = "cbc:SubTypeCode";
46+
private static final String XPATH_ADDITIONAL_LANGUAGE =
47+
"/*/AdditionalNoticeLanguage/ID/text()";
48+
49+
private static final XPath xpath = XPathFactory.newInstance().newXPath();
50+
51+
private final Element root;
52+
private final String xmlContents;
53+
54+
public NoticeDocument(final Path noticeXmlPath)
55+
throws ParserConfigurationException, SAXException, IOException {
56+
Validate.notNull(noticeXmlPath, "Undefined Notice XML file path");
57+
58+
if (!Files.isRegularFile(noticeXmlPath)) {
59+
throw new FileNotFoundException(noticeXmlPath.toString());
60+
}
61+
62+
this.xmlContents = Files.readString(noticeXmlPath, StandardCharsets.UTF_8);
63+
this.root = parseXmlRoot(this.xmlContents);
64+
}
65+
66+
public NoticeDocument(final InputStream noticeXmlInput)
67+
throws ParserConfigurationException, SAXException, IOException {
68+
Validate.notNull(noticeXmlInput, "Undefined Notice XML input");
69+
70+
this.xmlContents = new String(noticeXmlInput.readAllBytes(), StandardCharsets.UTF_8);
71+
this.root = parseXmlRoot(this.xmlContents);
72+
}
73+
74+
public NoticeDocument(final String noticeXmlContents)
75+
throws ParserConfigurationException, SAXException, IOException {
76+
Validate.notBlank(noticeXmlContents, "Invalid Notice XML contents");
77+
78+
this.xmlContents = noticeXmlContents;
79+
this.root = parseXmlRoot(this.xmlContents);
80+
}
81+
82+
private static Element parseXmlRoot(final String xmlContents)
83+
throws ParserConfigurationException, SAXException, IOException {
84+
try (InputStream input =
85+
new java.io.ByteArrayInputStream(xmlContents.getBytes(StandardCharsets.UTF_8))) {
86+
final Element root =
87+
SafeDocumentBuilder.buildSafeDocumentBuilderAllowDoctype().parse(input)
88+
.getDocumentElement();
89+
Validate.notNull(root, "No XML root found");
90+
return root;
91+
}
92+
}
93+
94+
/**
95+
* Gets the notice sub type from the notice XML.
96+
*
97+
* @return The notice sub type as found in the notice XML
98+
*/
99+
public String getNoticeSubType() {
100+
return Optional.ofNullable(this.root.getElementsByTagName(TAG_SUBTYPE_CODE))
101+
.map((final NodeList subTypeCodes) -> {
102+
Optional<String> result = Optional.empty();
103+
for (int i = 0; i < subTypeCodes.getLength(); i++) {
104+
result = Optional.ofNullable(subTypeCodes.item(i))
105+
.filter((final Node node) -> node.getAttributes() != null)
106+
.map(Node::getTextContent)
107+
.map(StringUtils::strip);
108+
}
109+
return result.orElse(null);
110+
})
111+
.filter(StringUtils::isNotBlank)
112+
.orElseThrow(() -> new RuntimeException("SubTypeCode not found in notice XML"));
113+
}
114+
115+
/**
116+
* Gets the eForms SDK version from the notice XML.
117+
*
118+
* @return The eForms SDK version as found in the notice XML
119+
*/
120+
public String getEformsSdkVersion() {
121+
return Optional.ofNullable(this.root.getElementsByTagName(TAG_SDK_VERSION))
122+
.filter((final NodeList nodes) -> nodes.getLength() == 1)
123+
.map((final NodeList nodes) -> Optional.ofNullable(nodes.item(0))
124+
.map(Node::getTextContent)
125+
.map(StringUtils::strip)
126+
.map((final String str) -> str.startsWith("eforms-sdk-")
127+
? str.substring("eforms-sdk-".length()) : str)
128+
.orElse(null))
129+
.filter(StringUtils::isNotBlank)
130+
.orElseThrow(() -> new RuntimeException("eForms SDK version not found in notice XML"));
131+
}
132+
133+
/**
134+
* Gets the primary language from the notice XML.
135+
*
136+
* @return The primary language
137+
*/
138+
public String getPrimaryLanguage() {
139+
return Optional
140+
.ofNullable(this.root.getElementsByTagName(TAG_PRIMARY_LANGUAGE))
141+
.map((final NodeList nodes) -> nodes.item(0))
142+
.map(Node::getTextContent)
143+
.orElse(null);
144+
}
145+
146+
/**
147+
* Gets the list of other languages from the notice XML.
148+
*
149+
* @return A list of other languages
150+
* @throws XPathExpressionException If an error occurs evaluating the XPath expression
151+
*/
152+
public List<String> getOtherLanguages() throws XPathExpressionException {
153+
return Optional
154+
.ofNullable(xpath.evaluateExpression(XPATH_ADDITIONAL_LANGUAGE,
155+
this.root.getOwnerDocument(), XPathNodes.class))
156+
.map((final XPathNodes nodes) -> {
157+
final List<String> languages = new ArrayList<>();
158+
nodes.forEach((final Node node) -> {
159+
if (StringUtils.isNotBlank(node.getTextContent())) {
160+
languages.add(node.getTextContent());
161+
}
162+
});
163+
return languages;
164+
})
165+
.orElseGet(ArrayList::new);
166+
}
167+
168+
/**
169+
* Gets the notice XML contents.
170+
*
171+
* @return The notice XML
172+
*/
173+
public String getXmlContents() {
174+
return this.xmlContents;
175+
}
176+
}

src/main/java/eu/europa/ted/eforms/sdk/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ The `eu.europa.ted.eforms.sdk` package contains the core classes and packages th
44

55
The main packages included here are:
66

7-
* `component`: Provides a solution for handling multiple major versions of the SDK in parallel.
7+
* `component`: Provides a solution for handling multiple major versions of the SDK in parallel.
8+
* `entity`: Provides abstract entity classes for representing SDK metadata (fields, nodes, codelists, notice subtypes, data types).
9+
* `repository`: Provides classes for reading SDK entities from JSON and Genericode files.
810
* `resource`: Provides a solution for automatically discovering and downloading new versions of the eForms SDK.
911

src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public class SdkConstants {
77
public static final String FIELDS_JSON_XML_STRUCTURE_KEY = "xmlStructure";
88
public static final String FIELDS_JSON_FIELDS_KEY = "fields";
99

10+
public static final String NOTICE_TYPES_JSON_SUBTYPES_KEY = "noticeSubTypes";
1011
public static final String NOTICE_TYPES_JSON_DOCUMENT_TYPES_KEY = "documentTypes";
1112
public static final String NOTICE_TYPES_JSON_DOCUMENT_TYPE_KEY = "documentType";
1213
public static final String NOTICE_TYPES_JSON_NAMESPACE_KEY = "namespace";

src/main/java/eu/europa/ted/eforms/sdk/SdkVersion.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@ public class SdkVersion implements Comparable<SdkVersion> {
1414

1515
private final Semver version;
1616

17+
private static final String SDK_PREFIX = "eforms-sdk-";
18+
1719
public SdkVersion(final String version) {
1820
Validate.notBlank(version, "Undefined version");
1921

22+
String normalized = version.startsWith(SDK_PREFIX) ? version.substring(SDK_PREFIX.length()) : version;
23+
2024
// LOOSE because we need to accept MAJOR.MINOR
21-
this.version = new Semver(version, SemverType.LOOSE);
25+
this.version = new Semver(normalized, SemverType.LOOSE);
2226

2327
// Check that we did get a MINOR part
2428
Validate.notNull(this.version.getMinor());

src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponent.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
import java.lang.annotation.RetentionPolicy;
77
import java.lang.annotation.Target;
88

9+
/**
10+
* Marks a class as an SDK component implementation for specific SDK versions.
11+
* Each annotated class must correspond to a specific component type and can optionally
12+
* specify a qualifier for multiple implementations of the same type.
13+
*/
914
@Inherited
1015
@Retention(RetentionPolicy.RUNTIME)
1116
@Target(ElementType.TYPE)

src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentDescriptor.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
import org.slf4j.Logger;
1515
import org.slf4j.LoggerFactory;
1616

17+
/**
18+
* Descriptor that uniquely identifies an SDK component by its version, type, and qualifier.
19+
* Used internally by {@link SdkComponentFactory} for component registry and lookup.
20+
*/
1721
public class SdkComponentDescriptor<T> implements Serializable {
1822
private static final long serialVersionUID = -6237218459963821365L;
1923

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

2832
private Class<T> implType;
2933

30-
public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType,
31-
Class<T> implType) {
32-
this(sdkVersion, componentType, "", implType);
33-
}
34-
34+
/**
35+
* Creates a descriptor with the specified SDK version, component type, and qualifier.
36+
*
37+
* @param sdkVersion the SDK version
38+
* @param componentType the component type
39+
* @param qualifier the qualifier (use empty string for default components)
40+
*/
3541
public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType, String qualifier,
3642
Class<T> implType) {
3743
this.sdkVersion = Validate.notBlank(sdkVersion, "Undefined SDK version");
@@ -40,6 +46,18 @@ public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType,
4046
this.implType = Validate.notNull(implType, "Undefined implementation type");
4147
}
4248

49+
/**
50+
* Creates a descriptor with the specified SDK version and component type.
51+
* The qualifier defaults to empty string.
52+
*
53+
* @param sdkVersion the SDK version
54+
* @param componentType the component type
55+
*/
56+
public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType,
57+
Class<T> implType) {
58+
this(sdkVersion, componentType, "", implType);
59+
}
60+
4361
@SuppressWarnings("unchecked")
4462
public T createInstance(Object... initArgs) throws InstantiationException {
4563
try {

0 commit comments

Comments
 (0)