Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/tedefo 2456 use of fields attribute information #19

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
76f000e
TEDEFO-2456 started work on this to investigate problems. Dynamically…
rouschr Aug 28, 2023
d1b1f13
TEDEFO-2456 improvements in attribute field generation (work in progr…
rouschr Aug 29, 2023
b08993b
TEDEFO-2456 work to use the attribute fields, some tests do not pass …
rouschr Aug 30, 2023
794125d
TEDEFO-2456 updated tests to align with data of SDK 1.9.0 and the XML…
rouschr Aug 31, 2023
aa4b0d3
TEDEFO-2456 various improvements and small fixes (work in progress).
rouschr Aug 31, 2023
41f3dee
TEDEFO-2456 various improvements in the back-end code. Removing some …
rouschr Sep 1, 2023
ab69d04
TEDEFO-2456 added autoDownload in application.yamnl. Added auto infer…
rouschr Sep 1, 2023
e02724d
TEDEFO-2630, TEDEFO-2631 various code improvements for readability, l…
rouschr Sep 7, 2023
fd02686
TEDEFO-2631 addition of skipIfNoValue, a boolean allowing to skip emp…
rouschr Sep 7, 2023
9ba8b02
TEDEFO-2630 added sortXml boolean, add fusion logic which fixes the c…
rouschr Sep 8, 2023
ca5acc4
TEDEFO-2456 removing unused import.
rouschr Sep 15, 2023
0004f2d
TEDEFO-2360 doing fixes testing against notice type 16, adding more d…
rouschr Oct 10, 2023
f0a3fe7
TEDEFO-2630 fixing nesting repeated nodes test, as it got broken thro…
rouschr Oct 11, 2023
52f8434
TEDEFO-2630 putting XML generation booleans into application.yaml
rouschr Oct 12, 2023
24fb6bb
TEDEFO-2630 improving the unit tests and use of constants, proper che…
rouschr Oct 12, 2023
636abc2
TEDEFO-2630 small change in the logs to make it more coherent.
rouschr Oct 12, 2023
c85b321
TEDEFO-2456 using SDK 1.11 instead of 1.9
rouschr Jun 6, 2024
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,5 @@ You will encounter these terms in the code and code comments:
* **Visual Model**: Representation of the form used to fill in a notice, found in the `notice-types` folder of SDK
* **Conceptual Model**: An intermediate representation made of fields and nodes, based on the SDK `fields.json`
* **Physical Model**: The representation of a notice in XML, see "XML Generation"
* **UI**: User Interface, in the editor demo this means the forms, buttons, links in the browser
* **metadata**: In the editor demo this is generally SDK data
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ public class EformsNoticeEditorApp implements CommandLineRunner {
@Value("${eforms.sdk.versions}")
private List<String> supportedSdks;

@Value("${eforms.sdk.autodownload}")
private boolean autoDownload;

public static void main(final String[] args) {
logger.info("STARTING eForms Notice Editor Demo Application");
// See README.md on how to run server.
// https://spring.io/guides/gs/serving-web-content/

// Here you have access to command line args.
// Here you have access to command line arguments.
// logger.debug("args={}", Arrays.toString(args));

SpringApplication.run(EformsNoticeEditorApp.class, args);
Expand All @@ -48,6 +51,19 @@ public void run(String... args) throws Exception {
Validate.notEmpty(eformsSdkDir, "Undefined eForms SDK path");
Validate.notNull(supportedSdks, "Undefined supported SDK versions");

logger.info("SDK autoDownload: {}", autoDownload);
if (autoDownload) {
// This automatically downloads the supported officially SDKs.
// You can test any SDK (even release candidates) by doing a git clone and putting the SDK
// files manually inside the folder where it would normally be downloaded.
autoDownloadSupportedSdks();
} else {
logger.info(
"Not automatically downloading SDKs, put files manually or set autoDownload to true in application.yaml");
}
}

private void autoDownloadSupportedSdks() {
for (final String sdkVersion : supportedSdks) {
try {
SdkDownloader.downloadSdk(new SdkVersion(sdkVersion), Path.of(eformsSdkDir));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Optional;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -21,17 +22,32 @@ public class XmlRestController implements AsyncConfigurer {
@Autowired
private XmlWriteService xmlService;

/**
* Enriches the XML for human readability but it becomes invalid. Also adds .dot files and other
* debug files in target.
*/
@Value("${xml.generation.debug}")
private boolean debug;

@Value("${xml.generation.skipIfEmpty}")
private boolean skipIfNoValue;

@Value("${xml.generation.sortXmlElements}")
private boolean sortXmlElements;

/**
* Save: Takes notice as JSON and builds notice XML. The SDK version is in the notice metadata.
*/
@RequestMapping(value = "/notice/save/validation/none", method = RequestMethod.POST,
produces = SdkService.MIME_TYPE_XML, consumes = SdkService.MIME_TYPE_JSON)
public void saveNotice(final HttpServletResponse response, final @RequestBody String noticeJson)
public void saveNotice(final HttpServletResponse response,
final @RequestBody String noticeJson)
throws Exception {
// Enriches the XML for human readability but it becomes invalid.
// Also adds .dot files in target.
final boolean debug = false;
xmlService.saveNoticeAsXml(Optional.of(response), noticeJson, debug);
// For the XML generation config booleans, see application.yaml
xmlService.saveNoticeAsXml(Optional.of(response), noticeJson,
debug,
skipIfNoValue,
sortXmlElements);
}

/**
Expand All @@ -42,7 +58,6 @@ public void saveNotice(final HttpServletResponse response, final @RequestBody St
produces = SdkService.MIME_TYPE_XML, consumes = SdkService.MIME_TYPE_JSON)
public void saveNoticeAndXsdValidate(final HttpServletResponse response,
final @RequestBody String noticeJson) throws Exception {
final boolean debug = false;
xmlService.validateUsingXsd(Optional.of(response), noticeJson, debug);
}

Expand All @@ -55,7 +70,6 @@ public void saveNoticeAndXsdValidate(final HttpServletResponse response,
produces = SdkService.MIME_TYPE_XML, consumes = SdkService.MIME_TYPE_JSON)
public void saveNoticeAndCvsValidate(final HttpServletResponse response,
final @RequestBody String noticeJson) throws Exception {
final boolean debug = false;
xmlService.validateUsingCvs(Optional.of(response), noticeJson, debug);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package eu.europa.ted.eforms.noticeeditor.helper.notice;

/**
* Conceptual field. Leaf in the conceptual tree. This holds non-metadata field information and the
* field id. This is not an SDK field, this only points to an SDK field to reference metadata.
* Conceptual field. Leaf in the conceptual tree. This holds non-metadata field information like the
* value and the associated SDK field id. This is not an SDK field, this only points to an SDK field
* to reference the metadata. A field item cannot have child items!
*/
public class ConceptTreeField extends ConceptTreeItem {
private final String value;
Expand All @@ -14,7 +15,9 @@ public ConceptTreeField(final String idUnique, final String idInSdkFieldsJson, f
}

/**
* For convenience and to make it clear that the ID in the SDK is the field ID in this case.
* For convenience and to make it clear that the ID in the SDK is the field ID in this case. It
* can be used to get general information about the field (data from fields.json). This does not
* include the counter.
*/
public String getFieldId() {
return idInSdkFieldsJson;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
import org.apache.commons.lang3.Validate;

/**
* Abstract item holding common information. References SDK metadata.
* Abstract item holding common conceptual information for a notice. References SDK metadata.
*/
public abstract class ConceptTreeItem {
/**
* Unique identifier among children at same level.
* Unique identifier among children at same level. Counter excluded.
*/
private final String idUnique;

/**
* This id is not unique as some concept items can be repeatead while still have the same metadata
* (pointing to same field or same node multiple times).
* (pointing to same field or same node multiple times). A counter helps to differentiate them.
*/
protected final String idInSdkFieldsJson;

Expand All @@ -32,12 +32,16 @@ protected ConceptTreeItem(final String idUnique, final String idInSdkFieldsJson,
}

/**
* @return Unique identifier among children at same level.
* @return Unique identifier among children at same level. Counter excluded.
*/
public String getIdUnique() {
return idUnique;
}

public String getIdInSdkFieldsJson() {
return idInSdkFieldsJson;
}

public int getCounter() {
return counter;
}
Expand All @@ -50,6 +54,7 @@ public String toString() {

@Override
public int hashCode() {
// Important: the counter is taken into account, this matters for repeatable items.
return Objects.hash(counter, idInSdkFieldsJson, idUnique);
}

Expand All @@ -65,6 +70,7 @@ public boolean equals(Object obj) {
return false;
}
ConceptTreeItem other = (ConceptTreeItem) obj;
// Important: the counter is taken into account, this matters for repeatable items.
return counter == other.counter && Objects.equals(idInSdkFieldsJson, other.idInSdkFieldsJson)
&& Objects.equals(idUnique, other.idUnique);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Conceptual node. This holds non-metadata information about a node. This is not an SDK node, this
* only points to an SDK node to reference metadata.
* only points to an SDK node to reference metadata. A node can have child items!
*/
public class ConceptTreeNode extends ConceptTreeItem {
private static final Logger logger = LoggerFactory.getLogger(ConceptTreeNode.class);

private final List<ConceptTreeField> conceptFields = new ArrayList<>();

/**
Expand All @@ -32,18 +36,20 @@ public ConceptTreeNode(final String idUnique, final String idInSdkFieldsJson, fi

/**
* @param item The item to add.
* @param sb
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "ITC_INHERITANCE_TYPE_CHECKING",
justification = "Spotbugs is confused, the check is done on the passed item, not the class.")
public final void addConceptItem(final ConceptTreeItem item) {
public final boolean addConceptItem(final ConceptTreeItem item, final StringBuilder sb) {
if (item instanceof ConceptTreeNode) {
addConceptNode((ConceptTreeNode) item, true);
} else if (item instanceof ConceptTreeField) {
addConceptField((ConceptTreeField) item);
} else {
throw new RuntimeException(
String.format("Unexpected item type for concept item=%s", item.getIdUnique()));
final boolean strict = true;
return addConceptNode((ConceptTreeNode) item, strict, sb);
}
if (item instanceof ConceptTreeField) {
return addConceptField((ConceptTreeField) item, sb);
}
throw new RuntimeException(
String.format("Unexpected item type for concept item=%s", item.getIdUnique()));
}

public Optional<ConceptTreeNode> findFirstByConceptNodeId(final String nodeId) {
Expand Down Expand Up @@ -71,44 +77,114 @@ public String getNodeId() {
return idInSdkFieldsJson;
}

public final void addConceptField(final ConceptTreeField conceptField) {
public final boolean addConceptField(final ConceptTreeField conceptField,
final StringBuilder sb) {
Validate.notNull(conceptField);
conceptFields.add(conceptField);
logger.debug("Added concept field uniqueId={} to fieldId={}", conceptField.getFieldId(),
this.getIdUnique(),
conceptField.getIdUnique());
sb.append("Added concept field: ")
.append(conceptField.getIdUnique())
.append(" to ").append(this.getIdUnique())
.append('\n');
return true;
}

public final void addConceptNode(final ConceptTreeNode conceptNode, final boolean strict) {
Validate.notNull(conceptNode);
final String otherNodeId = conceptNode.getNodeId();
final String thisNodeId = getNodeId();
if (otherNodeId.equals(thisNodeId)) {
/**
* @param cn The concept node to add
* @param strictAdd When true, if the item is already contained it will fail, set to true
* @param sb A string builder passed for debugging purposes
*/
public final boolean addConceptNode(final ConceptTreeNode cn, final boolean strictAdd,
final StringBuilder sb) {
Validate.notNull(cn);
final String nodeIdToAdd = cn.getNodeId();
final String nodeIdSelf = getNodeId();
if (nodeIdToAdd.equals(nodeIdSelf)) {
// Detect cycle, we have a tree, we do not want a graph, cannot self reference!
throw new RuntimeException(
String.format("Cannot have child=%s that is same as parent=%s (cycle), self reference.",
otherNodeId, thisNodeId));
nodeIdToAdd, nodeIdSelf));
}
if (conceptNode.isRepeatable()) {
if (cn.isRepeatable()) {
// It is repeatable, meaning it can exist multiple times, just add it.
conceptNodes.add(conceptNode);
return;
conceptNodes.add(cn);
sb.append("Added concept node (repeatable): ").append(cn.getIdUnique()).append(" to ")
.append(this.getIdUnique()).append('\n');
logger.debug("Added concept node uniqueId={} to nodeId={}", cn.getIdUnique(),
this.getNodeId());
return true;
}

// It is not repeatable. Is it already contained?
final boolean contained = conceptNodes.contains(conceptNode);
// It is not repeatable.
// Is it already contained?
final boolean cnAlreadyContained = conceptNodes.contains(cn);

// It should not already be contained.
if (strict) {
if (contained) {
if (strictAdd) {
// Strict add.
if (cnAlreadyContained) {
throw new RuntimeException(String.format(
"Conceptual model: node is not repeatable "
+ "but it is added twice, id=%s (nodeId=%s), parentId=%s",
conceptNode.getIdUnique(), conceptNode.getNodeId(), this.getIdUnique()));
+ "but it would be added twice, id=%s (nodeId=%s), parentId=%s",
cn.getIdUnique(), cn.getNodeId(), this.getIdUnique()));
}
conceptNodes.add(cn);
sb.append("Added concept node (not repeatable): ").append(cn.getIdUnique()).append(" to ")
.append(this.getIdUnique()).append('\n');
logger.debug("Added concept node uniqueId={} to nodeId={}", cn.getIdUnique(),
this.getNodeId());
return true;
}

// Non-strict add.
// We can add even if it is already contained.
// if (!cnAlreadyContained) {
// Add if not contained. Do not complain if already contained.

// We DO NOT want to add the entire thing blindly.

// Example: add X

// conceptNodes -> empty, X is not there just add X (no problem)

// conceptNodes -> (X -> Y -> 4141)

// Example: add X but X is already there
// conceptNodes -> (X -> Y -> Z -> 4242)

// We want to fuse / fusion of branches:
// conceptNodes -> X -> Y -> 4141
// .......................-> Z -> 4242

// So X and Y are reused (fused), and Z is attached to Y

if (cnAlreadyContained) {
final int indexOfCn = conceptNodes.indexOf(cn);
if (indexOfCn >= 0) {
// Iterative fusion logic:
// X could be already contained, but some child item like Y may not be there yet.
final ConceptTreeNode existingCn = conceptNodes.get(indexOfCn);
// We know that the branches uni-dimensional (flat), so a simple for loop with return should
// work. At least for the known cases this works.
for (final ConceptTreeNode cn2 : cn.getConceptNodes()) {
if (existingCn.addConceptNode(cn2, strictAdd, sb)) {
logger.debug("Fusion for uniqueId={}", cn2.getIdUnique());
return true;
}
}
}
conceptNodes.add(conceptNode);
} else if (!contained) {
// Non-strict.
// Add if not contained. Do not complain if already contained.
conceptNodes.add(conceptNode);
} else {
// Not contained yet, add it.
conceptNodes.add(cn);
sb.append("Added concept node (not repeatable)(bis): ").append(cn.getIdUnique())
.append(" to ")
.append(this.getIdUnique()).append('\n');
logger.debug("Added concept node uniqueId={} to nodeId={}", cn.getIdUnique(),
this.getNodeId());
}
return true;
}

public List<ConceptTreeField> getConceptFields() {
Expand All @@ -125,7 +201,8 @@ public boolean isRepeatable() {

@Override
public String toString() {
return "ConceptTreeNode [conceptFields=" + conceptFields + ", conceptNodes=" + conceptNodes
return "ConceptTreeNode id=" + this.idInSdkFieldsJson + " [conceptFields=" + conceptFields
+ ", conceptNodes=" + conceptNodes
+ ", repeatable=" + repeatable + "]";
}

Expand Down
Loading