Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
47 changes: 47 additions & 0 deletions src/main/java/eu/europa/ted/eforms/sdk/SdkConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public class SdkConstants {
public static final String FIELDS_DIR_NAME = "fields";

public static final String NODES_JSON_FILE_NAME = "nodes.json";

/**
* Key for the array of nodes of fields/fwd/nodes.json in SDK 2, where fields/fields.json uses
* {@link #FIELDS_JSON_XML_STRUCTURE_KEY} (TEDEFO-5228).
*/
public static final String NODES_JSON_NODES_KEY = "nodes";

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 DATA_TYPES_JSON_DATA_TYPES_KEY = "dataTypes";
Expand Down Expand Up @@ -58,6 +65,46 @@ public class SdkConstants {
*/
public static final String FWD_DIR_NAME = "fwd";

/**
* Key of the map of stable identifiers, in fields/fwd/fields.json and fields/fwd/nodes.json of
* SDK 2 (TEDEFO-1877, TEDEFO-5210). A stable identifier names the field or the node that plays a
* particular role, such as "disclosureDate", and stays the same throughout a major version of the
* SDK, whereas the identifier of the field or the node may change from one minor version to the
* next. The stable identifiers are plain strings, as the map is data and will grow
* (TEDEFO-5230).
*
* <p>
* The SDK 2 files call the map "specialPurpose".
* </p>
*/
public static final String STABLE_IDS_KEY = "specialPurpose";

/**
* Stable identifier of the field holding the disclosure group of a withheld field, which SDK 1
* named per field as privacy.unpublishedFieldId.
*/
public static final String STABLE_ID_DISCLOSURE_GROUP = "disclosureGroup";

/**
* Stable identifier of the field holding the justification code of a withheld field, which
* SDK 1 named per field as privacy.reasonCodeFieldId.
*/
public static final String STABLE_ID_DISCLOSURE_JUSTIFICATION_CODE =
"disclosureJustificationCode";

/**
* Stable identifier of the field holding the justification description of a withheld field,
* which SDK 1 named per field as privacy.reasonDescriptionFieldId.
*/
public static final String STABLE_ID_DISCLOSURE_JUSTIFICATION_DESCRIPTION =
"disclosureJustificationDescription";

/**
* Stable identifier of the field holding the disclosure date of a withheld field, which SDK 1
* named per field as privacy.publicationDateFieldId.
*/
public static final String STABLE_ID_DISCLOSURE_DATE = "disclosureDate";

private SdkConstants() {}

public enum SdkResource implements PathResource {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* factory.
*/
public enum SdkComponentType {
FIELD, NODE, CODELIST, NOTICE_TYPE, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR,
FIELD, NODE, CODELIST, NOTICE_TYPE, NODE_REPOSITORY, FIELD_REPOSITORY, EFX_EXPRESSION_TRANSLATOR, EFX_TEMPLATE_TRANSLATOR, EFX_RULES_TRANSLATOR,
EFX_COMPUTE_DEPENDENCY_EXTRACTOR, EFX_VALIDATION_DEPENDENCY_EXTRACTOR,
SYMBOL_RESOLVER, SCRIPT_GENERATOR, MARKUP_GENERATOR, VALIDATOR_GENERATOR;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
* Each field in the SDK has a type (e.g., "text", "date", "amount"). This entity captures
* type-level metadata such as the privacy masking value, the attribute that the fields of the type
* carry (e.g., the currency of an amount) with its data type, and the code list of the data types
* whose values come from one. Currently hardcoded; will be loaded from data-types.json when it is
* added to the SDK.
* whose values come from one. SDK 1 publishes no data types, so they come from the data-types.json
* resource of this library; SDK 2 publishes them in fields/fwd/data-types.json. Both have the same
* format, so a data type does not depend on the SDK version.
*/
public class SdkDataType {
private final String id;
Expand Down
108 changes: 104 additions & 4 deletions src/main/java/eu/europa/ted/eforms/sdk/entity/SdkField.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ public abstract class SdkField implements Comparable<SdkField> {
private final String codelistId;
private final boolean repeatable;
private final String privacyCode;
private final PrivacySettings privacySettings;
// Not final: SDK 2 no longer names the disclosure fields per field, so they are filled in after
// this field is read, from the stable identifiers of the SDK (TEDEFO-5230).
private PrivacySettings privacySettings;
private final String withholdingCondition;
private final String undisclosedFieldSelector;
private final List<String> attributes;
private final String attributeOf;
private final String attributeName;
Expand Down Expand Up @@ -119,6 +123,8 @@ protected SdkField(final String id, final String type, final String parentNodeId
this.repeatable = repeatable;
this.privacyCode = null;
this.privacySettings = null;
this.withholdingCondition = null;
this.undisclosedFieldSelector = null;
this.attributes = Collections.emptyList();
this.attributeOf = null;
this.attributeName = null;
Expand All @@ -132,9 +138,10 @@ protected SdkField(final JsonNode fieldNode) {
this.type = fieldNode.get("type").asText(null);
this.codelistId = extractCodelistId(fieldNode);
this.repeatable = extractRepeatable(fieldNode);
final JsonNode privacyNode = fieldNode.get("privacy");
this.privacyCode = privacyNode != null ? privacyNode.get("code").asText(null) : null;
this.privacySettings = extractPrivacy(privacyNode);
this.privacyCode = this.extractDisclosureGroupId(fieldNode);
this.privacySettings = this.extractDisclosureControl(fieldNode);
this.withholdingCondition = this.extractWithholdingCondition(fieldNode);
this.undisclosedFieldSelector = this.extractUndisclosedFieldSelector(fieldNode);
this.attributes = extractAttributes(fieldNode);
this.attributeOf = fieldNode.has("attributeOf") ? fieldNode.get("attributeOf").asText(null) : null;
this.attributeName = fieldNode.has("attributeName") ? fieldNode.get("attributeName").asText(null) : null;
Expand Down Expand Up @@ -180,6 +187,63 @@ protected boolean extractRepeatable(final JsonNode fieldNode) {
return valueNode.asBoolean(false);
}

/**
* Returns the identifier of the disclosure group of the field, from the code of the privacy block
* of fields/fields.json. SDK 2 gives it as the groupId of the disclosureControl block instead
* (TEDEFO-5229).
*
* <p>
* Called while this field is being constructed, so an override must read the given node only,
* and no state of its own.
* </p>
*/
protected String extractDisclosureGroupId(final JsonNode fieldNode) {
final JsonNode privacyNode = fieldNode.get("privacy");
return privacyNode != null ? privacyNode.get("code").asText(null) : null;
}

/**
* Returns the fields that hold the disclosure data of this field, from the privacy block of
* fields/fields.json. SDK 2 no longer gives them per field: they are the same for every field,
* and are linked to each field in TEDEFO-5230.
*
* <p>
* Called while this field is being constructed, so an override must read the given node only,
* and no state of its own.
* </p>
*/
protected PrivacySettings extractDisclosureControl(final JsonNode fieldNode) {
return this.extractPrivacy(fieldNode.get("privacy"));
}

/**
* Returns the EFX condition under which this field is withheld, for the fields that are withheld
* only for some of their instances, or null for every other field. Only SDK 2 gives it, in the
* disclosureControl block (TEDEFO-5229). It is not translated.
*
* <p>
* Called while this field is being constructed, so an override must read the given node only,
* and no state of its own.
* </p>
*/
protected String extractWithholdingCondition(final JsonNode fieldNode) {
return null;
}

/**
* Returns the EFX selector locating this field when it is withheld, for the fields that are
* withheld only for some of their instances, or null for every other field. Only SDK 2 gives it,
* in the disclosureControl block (TEDEFO-5229). It is not translated.
*
* <p>
* Called while this field is being constructed, so an override must read the given node only,
* and no state of its own.
* </p>
*/
protected String extractUndisclosedFieldSelector(final JsonNode fieldNode) {
return null;
}

protected PrivacySettings extractPrivacy(final JsonNode privacyNode) {
if (privacyNode == null) {
return null;
Expand Down Expand Up @@ -215,6 +279,15 @@ public String getType() {
return this.type;
}

/**
* Returns the type as the SDK declares it, without any version-specific mapping. Lets a version
* that must not inherit such a mapping bypass it (see SdkFieldV1's measure/duration mapping,
* which applies to SDK 1 only).
*/
protected final String getDeclaredType() {
return this.type;
}

public String getCodelistId() {
return this.codelistId;
}
Expand Down Expand Up @@ -275,6 +348,33 @@ public PrivacySettings getPrivacySettings() {
return this.privacySettings;
}

/**
* Sets the fields that hold the disclosure data of this field. SDK 1 names them in the privacy
* block of each withheld field, so they are read with the field; SDK 2 names them once, by their
* stable identifiers in fields/fwd/fields.json, so they are filled in afterwards (TEDEFO-5230).
*/
public void setDisclosureControl(final PrivacySettings disclosureControl) {
this.privacySettings = disclosureControl;
}

/**
* @return the EFX condition ({@code {fieldId} ${conditionEfx}}) under which this field is
* withheld, as the SDK gives it and untranslated, or null when this field is not withheld
* or is withheld for all of its instances. Only SDK 2 gives it (TEDEFO-5229).
*/
public String getWithholdingCondition() {
return this.withholdingCondition;
}

/**
* @return the EFX selector ({@code {ND-Root} &{/fieldId[conditionEfx]}}) locating this field
* when it is withheld, as the SDK gives it and untranslated, or null when this field is not
* withheld or is withheld for all of its instances. Only SDK 2 gives it (TEDEFO-5229).
*/
public String getUndisclosedFieldSelector() {
return this.undisclosedFieldSelector;
}

public SdkNode getParentNode() {
return this.parentNode;
}
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/eu/europa/ted/eforms/sdk/entity/v2/SdkFieldV2.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,26 @@
import eu.europa.ted.eforms.sdk.component.SdkComponentType;
import eu.europa.ted.eforms.sdk.entity.v1.SdkFieldV1;

/**
* A field of SDK 2, read from fields/fwd/fields.json (TEDEFO-5229).
*
* <p>
* Compared with fields/fields.json, the properties this entity reads differ as follows: repeatable
* is a plain boolean instead of an object with a value; the privacy block is replaced by
* disclosureControl, whose groupId is the privacy code and which also carries the two EFX
* expressions of the fields that are withheld only for some of their instances; and the four
* fields that held the disclosure data are no longer given per field, so they are not read here
* (TEDEFO-5230 links them to each field instead).
* </p>
*/
@SdkComponent(versions = {"2"}, componentType = SdkComponentType.FIELD)
public class SdkFieldV2 extends SdkFieldV1 {

/**
* Replaces the privacy block of fields/fields.json (TEDEFO-5128).
*/
private static final String DISCLOSURE_CONTROL = "disclosureControl";

private final String alias;

public SdkFieldV2(String id, String type, String parentNodeId, String xpathAbsolute,
Expand Down Expand Up @@ -40,4 +58,73 @@ public SdkFieldV2(
public String getAlias() {
return alias;
}

/**
* fields/fwd/fields.json gives repeatable as a plain boolean, where fields/fields.json gives an
* object with a value. Both are read, because an SDK 2 field is read from either file.
*/
@Override
protected boolean extractRepeatable(final JsonNode fieldNode) {
final JsonNode repeatableNode = fieldNode.get("repeatable");
if (repeatableNode != null && repeatableNode.isBoolean()) {
return repeatableNode.booleanValue();
}
return super.extractRepeatable(fieldNode);
}

/**
* fields/fwd/fields.json gives the identifier of the disclosure group as the groupId of the
* disclosureControl block, where fields/fields.json gives it as the code of the privacy block.
*/
@Override
protected String extractDisclosureGroupId(final JsonNode fieldNode) {
if (disclosureControlOf(fieldNode) == null) {
return super.extractDisclosureGroupId(fieldNode);
}
return textOfDisclosureControl(fieldNode, "groupId");
}

/**
* fields/fwd/fields.json no longer gives the fields that hold the disclosure data per field: they
* are the same for every field, and TEDEFO-5230 links them to each field instead. They are still
* read from the privacy block of fields/fields.json.
*/
@Override
protected PrivacySettings extractDisclosureControl(final JsonNode fieldNode) {
if (disclosureControlOf(fieldNode) == null) {
return super.extractDisclosureControl(fieldNode);
}
return null;
}

@Override
protected String extractWithholdingCondition(final JsonNode fieldNode) {
return textOfDisclosureControl(fieldNode, "withholdingCondition");
}

@Override
protected String extractUndisclosedFieldSelector(final JsonNode fieldNode) {
return textOfDisclosureControl(fieldNode, "undisclosedFieldSelector");
}

/**
* SDK 2 separates duration from measure, so the mapping of measure onto duration that
* {@link SdkFieldV1} applies for SDK 1 must not apply here.
*/
@Override
public String getType() {
return this.getDeclaredType();
}

private static JsonNode disclosureControlOf(final JsonNode fieldNode) {
return fieldNode.get(DISCLOSURE_CONTROL);
}

private static String textOfDisclosureControl(final JsonNode fieldNode, final String property) {
final JsonNode disclosureControl = disclosureControlOf(fieldNode);
if (disclosureControl == null || !disclosureControl.hasNonNull(property)) {
return null;
}
return disclosureControl.get(property).asText(null);
}
}
36 changes: 36 additions & 0 deletions src/main/java/eu/europa/ted/eforms/sdk/repository/MapFromJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger;
Expand All @@ -14,6 +18,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import eu.europa.ted.eforms.sdk.SdkConstants;

public abstract class MapFromJson<T> extends HashMap<String, T> {
private static final long serialVersionUID = 1L;
Expand Down Expand Up @@ -85,6 +90,37 @@ protected void populateMap(final JsonNode json, final Object... context)
populateMap(json);
}

/**
* Reads the map of stable identifiers ({@link SdkConstants#STABLE_IDS_KEY}), which
* fields/fwd/fields.json and fields/fwd/nodes.json of SDK 2 each start with.
*
* <p>
* Applications use the stable identifier, such as "disclosureDate", instead of hard-coding the
* identifier of the field or the node.
* </p>
*
* @param json the root of a file that may start with a map of stable identifiers
* @return the identifier of each field or node, by its stable identifier, in the order of the
* file, or an empty map for the SDK versions whose files carry no such map
*/
protected static Map<String, String> stableIdsOf(final JsonNode json) {
final JsonNode stableIds = json.get(SdkConstants.STABLE_IDS_KEY);
if (stableIds == null || !stableIds.isObject()) {
return Collections.emptyMap();
}

// Keep the order of the file, so that what is read back is what the SDK gives.
final Map<String, String> idByStableId = new LinkedHashMap<>();
final Iterator<String> names = stableIds.fieldNames();
while (names.hasNext()) {
final String stableId = names.next();
if (stableIds.hasNonNull(stableId)) {
idByStableId.put(stableId, stableIds.get(stableId).asText(null));
}
}
return Collections.unmodifiableMap(idByStableId);
}

/**
* @return A reusable Jackson object mapper instance.
*/
Expand Down
Loading
Loading