Skip to content

Commit 5eba815

Browse files
authored
Support conversion of ADL1.4 to ADL2 at-coded (#658)
* Be able to set adlVersion in the configuration: * Add configuration to convert adl1.4 into adl2 at-coded * Remake example archetypes * Fix conversion of valueSets * remove dutch comments * requested changes * Change NodeIdUtil codes to String * Fix issues * Fix issue that new nodes are created in specialisations * Fix tests * Revert gitignore change * Update .gitignore * Extract code to check code system and update documentation * Update tests * Update ADL14ToADL2Test.java * Make tests better * extract code that removed unnecessary codes * add comment * Update ADL14ToADL2Test.java * Requested changes * Update code after merge * Fix test now that the order of terminology codes is normal again * Update ArchetypeTerm.java * Update ArchetypeTerm.java * Update TerminologyRelation.java * Update TerminologyRelation.java
1 parent 3988de0 commit 5eba815

18 files changed

Lines changed: 7792 additions & 54 deletions

aom/src/main/java/com/nedap/archie/adl14/ADL14ConversionConfiguration.java

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.nedap.archie.adl14;
22

33
import com.nedap.archie.adl14.terms.TerminologyUriTemplate;
4+
import com.nedap.archie.rminfo.ArchieAOMInfoLookup;
45

56
import java.util.ArrayList;
67
import java.util.List;
@@ -21,21 +22,25 @@ public class ADL14ConversionConfiguration {
2122
*/
2223
private boolean applyDiff = true;
2324

24-
2525
/**
2626
* ADL 1.4 contains no rm release version, 2 does. So one needs to be added. Set to the desired rm_release. Defaults to 1.1.0
2727
*/
2828
private String rmRelease = "1.1.0";
2929

30+
/**
31+
* Set to the code system the ADL 1.4 archetype should be converted into. Options: ID_CODED, AT_CODED. Defaults to ID_CODED.
32+
*/
33+
private NodeIdCodeSystem nodeIdCodeSystem = NodeIdCodeSystem.ID_CODED;
34+
public enum NodeIdCodeSystem {
35+
ID_CODED,
36+
AT_CODED
37+
}
3038

39+
// GETTERS
3140
public List<TerminologyUriTemplate> getTerminologyConversionTemplates() {
3241
return terminologyConversionTemplates;
3342
}
3443

35-
public void setTerminologyConversionTemplates(List<TerminologyUriTemplate> terminologyConversionTemplates) {
36-
this.terminologyConversionTemplates = terminologyConversionTemplates;
37-
}
38-
3944
public TerminologyUriTemplate getTerminologyUriTemplate(String terminologyId, String version) {
4045
Optional<TerminologyUriTemplate> result = terminologyConversionTemplates.stream().filter(template ->
4146
template.getTerminologyId().equalsIgnoreCase(terminologyId) &&
@@ -52,23 +57,40 @@ public boolean isAllowDuplicateFieldNames() {
5257
return allowDuplicateFieldNames;
5358
}
5459

55-
public void setAllowDuplicateFieldNames(boolean allowDuplicateFieldNames) {
56-
this.allowDuplicateFieldNames = allowDuplicateFieldNames;
57-
}
58-
5960
public boolean isApplyDiff() {
6061
return applyDiff;
6162
}
6263

63-
public void setApplyDiff(boolean applyDiff) {
64-
this.applyDiff = applyDiff;
65-
}
66-
6764
public String getRmRelease() {
6865
return rmRelease;
6966
}
7067

68+
public NodeIdCodeSystem getNodeIdCodeSystem() {
69+
return nodeIdCodeSystem;
70+
}
71+
72+
public String getAdlVersion() {
73+
return ArchieAOMInfoLookup.ADL_VERSION;
74+
}
75+
76+
// SETTERS
77+
public void setTerminologyConversionTemplates(List<TerminologyUriTemplate> terminologyConversionTemplates) {
78+
this.terminologyConversionTemplates = terminologyConversionTemplates;
79+
}
80+
81+
public void setAllowDuplicateFieldNames(boolean allowDuplicateFieldNames) {
82+
this.allowDuplicateFieldNames = allowDuplicateFieldNames;
83+
}
84+
85+
public void setApplyDiff(boolean applyDiff) {
86+
this.applyDiff = applyDiff;
87+
}
88+
7189
public void setRmRelease(String rmRelease) {
7290
this.rmRelease = rmRelease;
7391
}
92+
93+
public void setNodeIdCodeSystem(NodeIdCodeSystem nodeIdCodeSystem) {
94+
this.nodeIdCodeSystem = nodeIdCodeSystem;
95+
}
7496
}

aom/src/main/java/com/nedap/archie/adl14/ADL14NodeIDConverter.java

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ public ADL14NodeIDConverter(MetaModelProvider metaModelProvider, Archetype arche
6565
this.termConstraintConverter = new ADL14TermConstraintConverter(this, archetype, flatParentArchetype);
6666
this.previousConversionApplier = new PreviousConversionApplier(this, archetype, oldLog);
6767
this.conversionResult = conversionResult;
68-
6968
}
7069

7170
public ADL14ConversionConfiguration getConversionConfiguration() {
@@ -154,7 +153,6 @@ private List<String> findUnnecessaryCodes(CObject cObject, Map<String, Archetype
154153
public static void convertTermDefinitions(Archetype archetype, Map<String, ConvertedCodeResult> convertedCodes, List<String> unnecessaryCodes) {
155154
archetype.getTerminology().getTermDefinitions().replaceAll((language, terms) -> {
156155
Map<String, ArchetypeTerm> newTerms = new LinkedHashMap<>();
157-
158156
for (Map.Entry<String, ArchetypeTerm> entry : terms.entrySet()) {
159157
String oldCode = entry.getKey();
160158
if (!unnecessaryCodes.contains(oldCode)) {
@@ -178,11 +176,8 @@ public static void convertTermDefinitions(Archetype archetype, Map<String, Conve
178176

179177
return newTerms;
180178
});
181-
182-
//the terminology can still contain old unused codes now. The archetype validation will warn about that later
183179
}
184180

185-
186181
/**
187182
* Replace old id's in term bindings with the new codes
188183
*/
@@ -248,7 +243,7 @@ private void generateMissingNodeIds(CObject cObject) {
248243
//if found, this is a specialization of said node and needs to be checked for differences and/or
249244
//given the same node id
250245
//if not found, generate/synthesize a new node id.
251-
String parentPath = AOMUtils.pathAtSpecializationLevel(cObject.getPathSegments(), archetype.specializationDepth() - 1);
246+
String parentPath = pathAtSpecializationLevel(cObject.getPathSegments(), archetype.specializationDepth() - 1);
252247

253248
CAttribute cAttributeInParent = flatParentArchetype.itemAtPath(parentPath);
254249
if (cAttributeInParent != null) {
@@ -305,7 +300,11 @@ private void createSpecialisedNodeId(CObject cObject, String path, List<CObject>
305300
* Object needs a new nodeId, generate the next valid nodeId and add in to the terminology
306301
*/
307302
private void synthesizeNodeId(CObject cObject, String path) {
308-
cObject.setNodeId(idCodeGenerator.generateNextIdCode());
303+
if (codeSystemIsIdCoded()) {
304+
cObject.setNodeId(idCodeGenerator.generateNextIdCode());
305+
} else {
306+
cObject.setNodeId(idCodeGenerator.generateNextValueCode());
307+
}
309308
CreatedCode createdCode = new CreatedCode(cObject.getNodeId(), ReasonForCodeCreation.C_OBJECT_WITHOUT_NODE_ID);
310309
createdCode.setRmTypeName(cObject.getRmTypeName());
311310
createdCode.setPathCreated(path);
@@ -352,7 +351,7 @@ private void convert(CObject cObject) {
352351
//VSSID validation does not exist in ADL 1.4. Fix it here
353352

354353
if (flatParentArchetype != null) {
355-
String parentPath = AOMUtils.pathAtSpecializationLevel(cObject.getPathSegments(), archetype.specializationDepth() - 1);
354+
String parentPath = pathAtSpecializationLevel(cObject.getPathSegments(), archetype.specializationDepth() - 1);
356355
CObject cObjectInParent = flatParentArchetype.itemAtPath(parentPath);
357356
if (cObjectInParent instanceof ArchetypeSlot && !cObjectInParent.getNodeId().equalsIgnoreCase(cObject.getNodeId())) {
358357
//specializing a node id for an archetype slot is not allowed in ADL 2. Set to parent node id.
@@ -429,16 +428,15 @@ private static void fixArchetypeSlotExpression(Expression expression) {
429428
}
430429

431430
/**
432-
* If the object has a nodeId
431+
* If the object has a nodeId & code system should be id coded
433432
* - replace it with a new nodeId
434433
* - store the old and new nodeId as a converted code
435434
*/
436435
private void calculateNewNodeId(CObject cObject) {
437-
if (cObject.getNodeId() != null) {
436+
if (cObject.getNodeId() != null && codeSystemIsIdCoded()) {
438437
String oldNodeId = cObject.getNodeId();
439438
String newNodeId = convertNodeId(oldNodeId);
440439
addConvertedCode(oldNodeId, newNodeId);
441-
442440
cObject.setNodeId(newNodeId);
443441
}
444442
}
@@ -469,7 +467,7 @@ public String convertNodeId(String oldNodeId) {
469467
/**
470468
* Convert old code into an at code
471469
*/
472-
protected String convertValueCode(String oldCode) {
470+
protected String convertIntoAtCode(String oldCode) {
473471
ConvertedCodeResult convertedCodeResult = convertedCodes.get(oldCode);
474472
if (convertedCodeResult != null && convertedCodeResult.hasValueCode()) {
475473
return convertedCodeResult.getValueCode();
@@ -501,18 +499,19 @@ public static String convertCode(String oldCode, String newCodePrefix) {
501499
nodeIdUtil.setPrefix(newCodePrefix); //will automatically strip the leading zeroes due to integer-parsing
502500
if (!oldCode.startsWith("at0.") && !oldCode.startsWith("ac0.")) {
503501
//a bit tricky, since the root of an archetype starts with at0000.0, but that's different from this I guess
504-
nodeIdUtil.getCodes().set(0, nodeIdUtil.getCodes().get(0) + 1); //increment with 1, old is 0-based
502+
nodeIdUtil.getCodes().set(0, String.valueOf(Integer.parseInt(nodeIdUtil.getCodes().get(0)) + 1)); // increment with 1, old is 0-based
505503
}
506504
return nodeIdUtil.toString();
507505
}
508506

509507
/**
510-
* Convert all old codes in a path in the new codes
508+
* Convert all old codes in a path in the new codes.
509+
* If the code system should be at coded, the code should stay the same.
511510
*/
512511
public String convertPath(String key) {
513512
APathQuery aPathQuery = new APathQuery(key);
514513
for (PathSegment segment : aPathQuery.getPathSegments()) {
515-
if (segment.getNodeId() != null) {
514+
if (codeSystemIsIdCoded() && segment.getNodeId() != null) {
516515
segment.setNodeId(convertNodeId(segment.getNodeId()));
517516
}
518517
}
@@ -533,4 +532,22 @@ public ADL2ConversionResult getConversionResult() {
533532
protected IdCodeGenerator getIdCodeGenerator() {
534533
return idCodeGenerator;
535534
}
535+
536+
/**
537+
*
538+
*/
539+
public boolean codeSystemIsIdCoded() {
540+
return conversionConfiguration.getNodeIdCodeSystem().equals(ADL14ConversionConfiguration.NodeIdCodeSystem.ID_CODED);
541+
}
542+
543+
/**
544+
* Returns the path at the given specialization level. Takes node system of converter into account.
545+
*/
546+
private String pathAtSpecializationLevel(List<PathSegment> pathSegments, int specializationLevel) {
547+
if (codeSystemIsIdCoded()) {
548+
return AOMUtils.pathAtSpecializationLevel(pathSegments, specializationLevel);
549+
} else {
550+
return AOMUtils.pathAtSpecializationLevelAtCoded(pathSegments, specializationLevel);
551+
}
552+
}
536553
}

aom/src/main/java/com/nedap/archie/adl14/ADL14TermConstraintConverter.java

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ public void convert() {
3939
}
4040

4141
private void convert(CObject cObject) {
42-
4342
if (cObject instanceof CTerminologyCode) {
4443
convertCTerminologyCode((CTerminologyCode) cObject);
4544
}
@@ -99,16 +98,26 @@ private void convertCTerminologyCode(CTerminologyCode cTerminologyCode) {
9998
if(isLocalCode && AOMUtils.isValueCode(firstConstraint)) {
10099
//local codes
101100
if(cTerminologyCode.getConstraint().size() == 1) {
102-
//do not create a value set, just convert the code
103-
String newCode = converter.convertValueCode(firstConstraint);
104-
converter.addConvertedCode(firstConstraint, newCode);
105-
cTerminologyCode.setConstraint(Lists.newArrayList(newCode));
101+
// do not create a value set, just convert the code
102+
// if the code system should be at coded, the code stays the same
103+
if (converter.codeSystemIsIdCoded()) {
104+
String newCode = converter.convertIntoAtCode(firstConstraint);
105+
converter.addConvertedCode(firstConstraint, newCode);
106+
cTerminologyCode.setConstraint(Lists.newArrayList(newCode));
107+
}
106108
} else {
109+
// Create a valueSet for these terminology codes
107110
Set<String> localCodes = new LinkedHashSet<>();
108111
for(String code:cTerminologyCode.getConstraint()) {
109-
String newCode = converter.convertValueCode(code);
110-
converter.addConvertedCode(code, newCode);
111-
localCodes.add(newCode);
112+
if (converter.codeSystemIsIdCoded()) {
113+
// If the code system should be id coded, we need to convert the local codes into at codes
114+
String newCode = converter.convertIntoAtCode(code);
115+
converter.addConvertedCode(code, newCode);
116+
localCodes.add(newCode);
117+
} else {
118+
// If the code system should be at coded, we can keep the local codes as they are
119+
localCodes.add(code);
120+
}
112121
}
113122

114123
ValueSet valueSet = findOrCreateValueSet(cTerminologyCode.getArchetype(), localCodes, cTerminologyCode);
@@ -172,7 +181,7 @@ private void convertCTerminologyCode(CTerminologyCode cTerminologyCode) {
172181
if(cTerminologyCode.getAssumedValue() != null) {
173182
TerminologyCode assumedValue = cTerminologyCode.getAssumedValue();
174183
if(isLocalCode) {
175-
String newCode = converter.convertValueCode(assumedValue.getCodeString());
184+
String newCode = converter.convertIntoAtCode(assumedValue.getCodeString());
176185
assumedValue.setCodeString(newCode);
177186
assumedValue.setTerminologyId(null);
178187
} else {
@@ -323,11 +332,17 @@ protected ArchetypeTerm getTerm(String language, CObject owningConstraint) {
323332
while(cObject != null) {
324333
if (cObject.getNodeId() != null) {
325334
String oldCode = converter.getOldCodeForNewCode(cObject.getNodeId());
326-
if(oldCode != null && archetype.getTerminology().getTermDefinition(language, oldCode) != null) {
335+
if (oldCode != null && archetype.getTerminology().getTermDefinition(language, oldCode) != null) {
327336
ArchetypeTerm term = archetype.getTerminology().getTermDefinition(language, oldCode);
328337
if(term != null) {
329338
return term;
330339
}
340+
} else if (archetype.getTerminology().getTermDefinition(language, cObject.getNodeId()) != null) {
341+
// It is not converted, so just use the node id of the object to find the term
342+
ArchetypeTerm term = archetype.getTerminology().getTermDefinition(language, cObject.getNodeId());
343+
if(term != null) {
344+
return term;
345+
}
331346
}
332347
}
333348
cObject = cObject.getParent() == null ? null : cObject.getParent().getParent();

aom/src/main/java/com/nedap/archie/aom/utils/AOMUtils.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,25 @@ public static String pathAtSpecializationLevel(List<PathSegment> pathSegments, i
7878
return PathUtil.getPath(pathSegments);
7979
}
8080

81+
public static String pathAtSpecializationLevelAtCoded(List<PathSegment> pathSegments, int level) {
82+
for(PathSegment segment:pathSegments) {
83+
if(segment.getNodeId() != null && AOMUtils.isValidADL14Code(segment.getNodeId()) && AOMUtils.getSpecializationDepthFromCode(segment.getNodeId()) > level) {
84+
segment.setNodeId(codeAtLevel(segment.getNodeId(), level));
85+
}
86+
}
87+
return PathUtil.getPath(pathSegments);
88+
}
89+
8190
public static String codeAtLevel(String nodeId, int level) {
8291
NodeIdUtil nodeIdUtil = new NodeIdUtil(nodeId);
83-
List<Integer> codes = new ArrayList<>();
84-
for(int i = 0; i <= level && i < nodeIdUtil.getCodes().size();i++) {
92+
List<String> codes = new ArrayList<>();
93+
for(int i = 0; i <= level && i < nodeIdUtil.getCodes().size(); i++) {
8594
codes.add(nodeIdUtil.getCodes().get(i));
8695
}
8796
//remove leading .0 codes - they are not present in the code at the given level
8897
int numberOfCodesToRemove = 0;
8998
for(int i = codes.size()-1; i >= 0 ; i--) {
90-
if(codes.get(i).intValue() == 0) {
99+
if("0".equals(codes.get(i))) {
91100
numberOfCodesToRemove++;
92101
} else {
93102
break;
@@ -117,7 +126,7 @@ public static CodeRedefinitionStatus getSpecialisationStatusFromCode(String node
117126
if(specialisationDepth > getSpecializationDepthFromCode(nodeId)) {
118127
return CodeRedefinitionStatus.INHERITED;
119128
} else {
120-
boolean codeDefinedAtThisLevel = codeIndexAtLevel(nodeId, specialisationDepth) > 0;
129+
boolean codeDefinedAtThisLevel = !"0".equals(codeIndexAtLevel(nodeId, specialisationDepth));
121130
if(codeDefinedAtThisLevel) {
122131
if(specialisationDepth > 0 && codeExistsAtLevel(nodeId, specialisationDepth-1)) {
123132
return CodeRedefinitionStatus.REDEFINED;
@@ -133,7 +142,7 @@ public static CodeRedefinitionStatus getSpecialisationStatusFromCode(String node
133142
}
134143
}
135144

136-
public static int codeIndexAtLevel(String nodeId, int specialisationDepth) {
145+
public static String codeIndexAtLevel(String nodeId, int specialisationDepth) {
137146
NodeIdUtil nodeIdUtil = new NodeIdUtil(nodeId);
138147
if(specialisationDepth < 0 || specialisationDepth >= nodeIdUtil.getCodes().size()) {
139148
throw new IllegalArgumentException("code is not valid at specialization depth " + specialisationDepth);
@@ -377,13 +386,12 @@ public static boolean isPathInArchetypeOrRm(MetaModel metaModel, String path, Ar
377386
* @return
378387
*/
379388
public static String getCodeInNearestParent(String nodeId) {
380-
381389
NodeIdUtil nodeIdUtil = new NodeIdUtil(nodeId);
382390

383-
List<Integer> codes = nodeIdUtil.getCodes();
391+
List<String> codes = nodeIdUtil.getCodes();
384392
int newDepth = 0;
385393
for(int i = codes.size()-2; i >= 0; i--) {
386-
if(codes.get(i) != 0) {
394+
if(!"0".equals(codes.get(i))) {
387395
newDepth = i;
388396
break;
389397
}

0 commit comments

Comments
 (0)