Skip to content

Feature/tedefo 4779 forward compatibility features, 4 tickets - #73

Open
rouschr wants to merge 13 commits into
developfrom
feature/TEDEFO-4779-forward-compatibility-features
Open

rouschr wants to merge 13 commits into
developfrom
feature/TEDEFO-4779-forward-compatibility-features

Conversation

@rouschr

@rouschr rouschr commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

This is part of:
https://citnet.tech.ec.europa.eu/CITnet/confluence/spaces/TEDEFO/pages/1571268369/SDK+2+Forward-Compatible+Metadata+%E2%80%94+Tickets+Implementation+Plan

4 TEDEFO-5231 ECL The SDK 2 data types from fields/fwd/data-types.json

5 TEDEFO-5228 ECL The SDK 2 nodes from fields/fwd/nodes.json

6 TEDEFO-5229 ECL The SDK 2 fields from fields/fwd/fields.json, with groupId and the two disclosure expressions

7 TEDEFO-5230 ECL The special purpose maps, and the four disclosure fields attached to each field that can be withheld

Rework

The commits after the first four keep what the tickets ask for, but change how the library decides what to read for each SDK version, and give the new API the SDK 2 names:

  • Repositories per SDK version instead of version checks. SdkNodeRepositoryV1/V2 and SdkFieldRepositoryV1/V2 are registered with @SdkComponent, and SdkRepositoryFactory gives the ones of an SDK version, built from the folder of that SDK. This replaces the three checks of the major version and the forSdk methods.
  • Data types without a version, as decided in TEDEFO-5239 Create the data types without an SDK version #72: SdkDataTypeV1/V2 and the DATA_TYPE component type are removed. SdkDataTypeRepository(sdkPath) reads the SDK's fields/fwd/data-types.json, or this library's resource when the SDK publishes none (SDK 1).
  • The existing constructors of the node and field repositories are unchanged and no longer deprecated, so the EFX Toolkit, the Notice Viewer, the MDM and the SDK Analyzer keep working as they are.
  • Stable identifiers. The specialPurpose maps are read as stable identifiers: getStableIds(), getFieldIdByStableId(…) and getNodeIdByStableId(…), with the constants STABLE_IDS_KEY and STABLE_ID_DISCLOSURE_…. STABLE_IDS_KEY keeps the value "specialPurpose" until the MDM export renames the key. The reader has moved from the SpecialPurpose class into MapFromJson.
  • SDK 2 names for the new disclosure control methods: extractDisclosureGroupId, extractDisclosureControl and setDisclosureControl. The privacy names released in 1.9 are unchanged; TEDEFO-5281 will deprecate them.

All 131 tests pass, the API check passes against 1.9.1, and the EFX Toolkit, the Notice Viewer and the MDM pass their tests with this version. The renames touch only methods that none of them uses.

The data types were always read from the data-types.json resource of
this library, whatever the SDK version. SDK 2 publishes them in
fields/fwd/data-types.json, so reading them from there gives the EFX
Toolkit the masking values, attributes and code lists of the SDK
instead of a copy kept here. SDK 1 publishes none, so it keeps
reading the resource.

- Add SdkDataTypeRepository(String sdkVersion, Path sdkRootPath),
  which decides where the data types come from: the resource for
  SDK 1, fields/fwd/data-types.json (SdkResource.FIELDS_FWD_DATA_TYPES,
  already defined) for SDK 2. The file has the same format as the
  resource, so the loop that creates the data types is the same for
  both. The version is decided here so that callers do not have to
  check it.
- Give each SDK version its own data type, as the other entities that
  differ per version have: SdkComponentType.DATA_TYPE,
  SdkEntityFactory.getSdkDataType(String, JsonNode), and the
  SdkDataTypeV1/SdkDataTypeV2 implementations. SdkDataTypeV2 adds
  nothing yet; it exists so that the two versions can diverge without
  changing the callers.
- Deprecate the no-arg constructor in favour of the version-aware one.
  It, createDefault() and SdkDataType itself are left as they were, so
  the existing callers (SdkSymbolResolver, SdkAnalyzerSymbolResolver)
  are unaffected and the change stays binary compatible.

Tests: the existing assertions now run through the SDK 1 path, which
pins that nothing changes for SDK 1. New tests show that SDK 2 reads
the data types, their attributes and their code lists from the SDK —
the fixture's masking value differs from the resource's, so only the
file can be the source — that the properties a data type does not
read are ignored, and that each version gets its own implementation.
The nodes of both SDK 1 and SDK 2 were read from the xmlStructure
array of fields/fields.json. SDK 2 publishes its nodes in the nodes
array of fields/fwd/nodes.json, in the forward-compatible structure
planned for SDK 3, so for SDK 2 they are now read from there. SDK 1
keeps reading fields/fields.json.

The nodes have the same properties in both files, so the node
entities do not change.

- Add SdkNodeRepository.forSdk(String sdkVersion, Path sdkRootPath),
  which decides which file the nodes come from: fields/fields.json
  (xmlStructure) for SDK 1, fields/fwd/nodes.json (nodes) for SDK 2,
  using the SdkResource constants that already exist. The version is
  decided here so that callers do not have to check it.
- The array key travels to populateMap as context, the mechanism
  MapFromJson already offers and SdkFieldRepository already uses: it
  has to be known while the map is being populated, which happens
  during the call to super. Keying it off the SDK version instead
  would have broken the existing constructor for SDK 2, which is
  given a fields.json.
- Deprecate the (sdkVersion, jsonPath) constructor in favour of
  forSdk. It still reads the xmlStructure array of the file it is
  given, so the existing callers (SdkSymbolResolver,
  SdkAnalyzerSymbolResolver) are unaffected and the change stays
  binary compatible.
- Add SdkConstants.NODES_JSON_NODES_KEY for the array of the fwd file.

Tests: a new SdkNodeRepositoryTest over an SDK folder holding both
files, which hold different nodes on purpose — the fwd file has a node
that fields.json does not, so the tests can tell which file was read.
They cover both sources, that the node properties agree between the
two, that the parents are wired either way (including the second pass,
for a node declared before its parent), that each version still gets
its own node implementation, and that the deprecated constructor is
unchanged.
The fields of both SDK 1 and SDK 2 were read from fields/fields.json.
SDK 2 publishes its fields in fields/fwd/fields.json, where some of
the properties the ECL reads have a different structure, so for SDK 2
they are now read from there. SDK 1 keeps reading fields/fields.json.

- Add SdkFieldRepository.forSdk(sdkVersion, sdkRootPath, nodes), which
  decides which file the fields come from, and links them to the nodes
  of the same SDK version (TEDEFO-5228). The array of fields has the
  same key in both files, so only the file differs.
- Give SdkField an override seam for the properties whose structure
  differs: extractPrivacyCode, extractPrivacySettings,
  extractWithholdingCondition and extractUndisclosedFieldSelector,
  alongside the extractRepeatable that was already there. They default
  to what fields/fields.json gives, and are called while the field is
  being constructed, so an override reads the given node only.
- Read the two EFX expressions of the fields that are withheld only
  for some of their instances, as they are and untranslated:
  SdkField.getWithholdingCondition() and getUndisclosedFieldSelector().
  The EFX Toolkit needs them (TEDEFO-5129).
- In SdkFieldV2, read the disclosureControl block: its groupId is the
  privacy code, and it carries the two expressions. The four fields
  that held the disclosure data are no longer given per field, so they
  are not read from it (TEDEFO-5230 links them instead).
- SdkFieldV2 reads BOTH shapes, because an SDK 2 field is read from
  either file: fields/fields.json still gives a privacy block and an
  object-shaped repeatable, and that is what the EFX Toolkit still
  feeds it until TEDEFO-5232 moves it to forSdk. Each override takes
  the fwd shape when it is there and falls back to the inherited one
  otherwise.
- Stop SdkFieldV2 inheriting the mapping of measure onto duration that
  SdkFieldV1 applies: SDK 1 had no duration type, SDK 2 separates the
  two (TEDEFO-4923), so an SDK 2 field typed measure reported duration.
  SdkField.getDeclaredType() gives a version the type the SDK declares,
  without the mapping. Reparenting SdkFieldV2 onto SdkField, which is
  where this belongs, is a binary incompatible change and has to wait
  for the major version.

Tests: a new SdkFieldRepositoryTest over an SDK folder holding both
files, which hold different fields on purpose, so the tests can tell
which file was read. They cover both sources, repeatable in both
shapes, the privacy code from groupId and from privacy.code, the two
expressions present on a conditionally withheld field and absent
otherwise, the disclosure fields still read for SDK 1 but not for
SDK 2, the parent node linking, the measure/duration split per
version, and the deprecated constructor unchanged.
…re fields

fields/fwd/fields.json and fields/fwd/nodes.json each start with a
specialPurpose map, which names the fields and nodes that have a
special role by a key such as disclosureDate, so that applications use
the key instead of hard-coding the identifier.

Among them are the four disclosure fields. fields/fields.json names
them in the privacy block of every withheld field; fields/fwd/fields.json
no longer does, because they are the same for every field. The EFX
Toolkit resolves the privacy properties of EFX 2 through the privacy
settings of each field, so they have to stay available per field.

- Read both maps in full, in the order of the file, and look them up by
  key: SdkFieldRepository.getSpecialPurposeFieldId(key) and
  SdkNodeRepository.getSpecialPurposeNodeId(key), with the whole map
  available too. An unknown key gives no result and is not an error,
  and so does an SDK version whose files carry no such map.
- Give every field that can be withheld and does not name them itself
  the four disclosure fields of the map. The pass that resolves the
  identifiers to fields then runs as it does for SDK 1, so the Toolkit
  sees the same thing either way. They are read from the map only,
  with no fallback to a privacy block, as the ticket asks.
- Each withheld field gets its own PrivacySettings: the identifiers are
  resolved to fields on them, so one shared instance would make the
  fields interfere.
- SdkField.privacySettings is no longer final, with a setter, because
  SDK 2 fills them in after the field is read.
- Add the keys to SdkConstants, and a package-private SpecialPurpose
  reader so both repositories parse the map the same way.

Note the specialPurpose field of each repository has no initialiser on
purpose: it is assigned while the map of entities is being populated,
which happens during the call to super, and a field initialiser would
run after that call and undo it.

Tests: this reverses one assertion of TEDEFO-5229, deliberately — an
SDK 2 withheld field had no privacy settings there, and now it has them
from the map. The fixtures gain the two maps (the shape is key to
identifier, which the placeholders had backwards) and the four notice
fields, so the linking resolves end to end. New tests cover the lookup
for fields and for nodes, an unknown key, an SDK 1 folder, the four
identifiers and the fields they resolve to, a field that cannot be
withheld, and that two withheld fields do not share their settings.
@rouschr rouschr self-assigned this Oct 8, 2026
@rousso
rousso self-requested a review October 12, 2026 07:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants