Fix FhirTerser to handle Enumeration correctly (#5726)

* Add test for CDA with binary database blog storage and some logging for the Terser.

* More logging and a new FhirTerser test.

* Add more comments and prove Enumeration is a valid subtype of Type.

* More logging and comments.

* James solution:  Comment out some code.  Spotless.

* Get rid of logging and TODOs.  Use AtomicBoolean for test.

* Try just commenting out the continue; line.

* Integrate changes from ja_20240222_choice_specialization_fix.

* Spotless.

* Changelog and fix for bad import.

* Code review feedback.
This commit is contained in:
Luke deGruchy 2024-02-26 10:10:12 -05:00 committed by GitHub
parent d8f6c10df2
commit 497b9f2e53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 100 additions and 48 deletions

View File

@ -30,7 +30,6 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -46,13 +45,15 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition {
void sealAndInitialize( void sealAndInitialize(
FhirContext theContext, FhirContext theContext,
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
List<Class<? extends IBase>> choiceTypes = new ArrayList<Class<? extends IBase>>(); List<Class<? extends IBase>> choiceTypes = new ArrayList<>();
List<Class<? extends IBase>> specializationChoiceTypes = new ArrayList<>();
for (Class<? extends IBase> next : theClassToElementDefinitions.keySet()) { for (Class<? extends IBase> next : theClassToElementDefinitions.keySet()) {
if (next.equals(XhtmlDt.class)) { if (next.equals(XhtmlDt.class)) {
continue; continue;
} }
boolean isSpecialization = false;
BaseRuntimeElementDefinition<?> nextDef = theClassToElementDefinitions.get(next); BaseRuntimeElementDefinition<?> nextDef = theClassToElementDefinitions.get(next);
if (nextDef instanceof IRuntimeDatatypeDefinition) { if (nextDef instanceof IRuntimeDatatypeDefinition) {
if (((IRuntimeDatatypeDefinition) nextDef).isSpecialization()) { if (((IRuntimeDatatypeDefinition) nextDef).isSpecialization()) {
@ -60,7 +61,7 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition {
* Things like BoundCodeDt shoudn't be considered as valid options for an "any" choice, since * Things like BoundCodeDt shoudn't be considered as valid options for an "any" choice, since
* we'll already have CodeDt as an option * we'll already have CodeDt as an option
*/ */
continue; isSpecialization = true;
} }
} }
@ -68,28 +69,36 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition {
|| IDatatype.class.isAssignableFrom(next) || IDatatype.class.isAssignableFrom(next)
|| IBaseDatatype.class.isAssignableFrom(next) || IBaseDatatype.class.isAssignableFrom(next)
|| IBaseReference.class.isAssignableFrom(next)) { || IBaseReference.class.isAssignableFrom(next)) {
choiceTypes.add(next); if (isSpecialization) {
} specializationChoiceTypes.add(next);
}
Collections.sort(choiceTypes, new Comparator<Class<?>>() {
@Override
public int compare(Class<?> theO1, Class<?> theO2) {
boolean o1res = IResource.class.isAssignableFrom(theO1);
boolean o2res = IResource.class.isAssignableFrom(theO2);
if (o1res && o2res) {
return theO1.getSimpleName().compareTo(theO2.getSimpleName());
} else if (o1res) {
return -1;
} else if (o1res == false && o2res == false) {
return 0;
} else { } else {
return 1; choiceTypes.add(next);
} }
} }
}); }
setChoiceTypes(choiceTypes); choiceTypes.sort(new ResourceTypeNameComparator());
specializationChoiceTypes.sort(new ResourceTypeNameComparator());
setChoiceTypes(choiceTypes, specializationChoiceTypes);
super.sealAndInitialize(theContext, theClassToElementDefinitions); super.sealAndInitialize(theContext, theClassToElementDefinitions);
} }
private static class ResourceTypeNameComparator implements Comparator<Class<?>> {
@Override
public int compare(Class<?> theO1, Class<?> theO2) {
boolean o1res = IResource.class.isAssignableFrom(theO1);
boolean o2res = IResource.class.isAssignableFrom(theO2);
if (o1res && o2res) {
return theO1.getSimpleName().compareTo(theO2.getSimpleName());
} else if (o1res) {
return -1;
} else if (!o2res) {
return 0;
} else {
return 1;
}
}
}
} }

View File

@ -22,7 +22,9 @@ package ca.uhn.fhir.context;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Description;
import jakarta.annotation.Nonnull;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseReference;
@ -45,6 +47,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition; private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition;
private String myReferenceSuffix; private String myReferenceSuffix;
private List<Class<? extends IBaseResource>> myResourceTypes; private List<Class<? extends IBaseResource>> myResourceTypes;
private List<Class<? extends IBase>> mySpecializationChoiceTypes = Collections.emptyList();
/** /**
* Constructor * Constructor
@ -70,8 +73,13 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
} }
void setChoiceTypes(List<Class<? extends IBase>> theChoiceTypes) { void setChoiceTypes(
@Nonnull List<Class<? extends IBase>> theChoiceTypes,
@Nonnull List<Class<? extends IBase>> theSpecializationChoiceTypes) {
Validate.notNull(theChoiceTypes, "theChoiceTypes must not be null");
Validate.notNull(theSpecializationChoiceTypes, "theSpecializationChoiceTypes must not be null");
myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); myChoiceTypes = Collections.unmodifiableList(theChoiceTypes);
mySpecializationChoiceTypes = Collections.unmodifiableList(theSpecializationChoiceTypes);
} }
public List<Class<? extends IBase>> getChoices() { public List<Class<? extends IBase>> getChoices() {
@ -96,14 +104,28 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
void sealAndInitialize( void sealAndInitialize(
FhirContext theContext, FhirContext theContext,
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); myNameToChildDefinition = new HashMap<>();
myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>(); myDatatypeToElementName = new HashMap<>();
myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>(); myDatatypeToElementDefinition = new HashMap<>();
myResourceTypes = new ArrayList<Class<? extends IBaseResource>>(); myResourceTypes = new ArrayList<>();
myReferenceSuffix = "Reference"; myReferenceSuffix = "Reference";
for (Class<? extends IBase> next : myChoiceTypes) { sealAndInitializeChoiceTypes(theContext, theClassToElementDefinitions, mySpecializationChoiceTypes, true);
sealAndInitializeChoiceTypes(theContext, theClassToElementDefinitions, myChoiceTypes, false);
myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition);
myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName);
myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition);
myResourceTypes = Collections.unmodifiableList(myResourceTypes);
}
private void sealAndInitializeChoiceTypes(
FhirContext theContext,
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions,
List<Class<? extends IBase>> choiceTypes,
boolean theIsSpecilization) {
for (Class<? extends IBase> next : choiceTypes) {
String elementName = null; String elementName = null;
BaseRuntimeElementDefinition<?> nextDef; BaseRuntimeElementDefinition<?> nextDef;
@ -112,8 +134,10 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
elementName = getElementName() + StringUtils.capitalize(next.getSimpleName()); elementName = getElementName() + StringUtils.capitalize(next.getSimpleName());
nextDef = findResourceReferenceDefinition(theClassToElementDefinitions); nextDef = findResourceReferenceDefinition(theClassToElementDefinitions);
myNameToChildDefinition.put(getElementName() + "Reference", nextDef); if (!theIsSpecilization) {
myNameToChildDefinition.put(getElementName() + "Resource", nextDef); myNameToChildDefinition.put(getElementName() + "Reference", nextDef);
myNameToChildDefinition.put(getElementName() + "Resource", nextDef);
}
myResourceTypes.add((Class<? extends IBaseResource>) next); myResourceTypes.add((Class<? extends IBaseResource>) next);
@ -147,21 +171,23 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
} }
// I don't see how elementName could be null here, but eclipse complains.. // I don't see how elementName could be null here, but eclipse complains..
if (elementName != null) { if (!theIsSpecilization) {
if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) { if (elementName != null) {
if (!myNameToChildDefinition.containsKey(elementName) || !nonPreferred) {
myNameToChildDefinition.put(elementName, nextDef);
}
}
/*
* If this is a resource reference, the element name is "fooNameReference"
*/
if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) {
next = theContext.getVersion().getResourceReferenceType();
elementName = getElementName() + myReferenceSuffix;
myNameToChildDefinition.put(elementName, nextDef); myNameToChildDefinition.put(elementName, nextDef);
} }
} }
/*
* If this is a resource reference, the element name is "fooNameReference"
*/
if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) {
next = theContext.getVersion().getResourceReferenceType();
elementName = getElementName() + myReferenceSuffix;
myNameToChildDefinition.put(elementName, nextDef);
}
myDatatypeToElementDefinition.put(next, nextDef); myDatatypeToElementDefinition.put(next, nextDef);
if (myDatatypeToElementName.containsKey(next)) { if (myDatatypeToElementName.containsKey(next)) {
@ -175,11 +201,6 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
myDatatypeToElementName.put(next, elementName); myDatatypeToElementName.put(next, elementName);
} }
} }
myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition);
myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName);
myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition);
myResourceTypes = Collections.unmodifiableList(myResourceTypes);
} }
public List<Class<? extends IBaseResource>> getResourceTypes() { public List<Class<? extends IBaseResource>> getResourceTypes() {
@ -188,8 +209,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
@Override @Override
public String getChildNameByDatatype(Class<? extends IBase> theDatatype) { public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
String retVal = myDatatypeToElementName.get(theDatatype); return myDatatypeToElementName.get(theDatatype);
return retVal;
} }
@Override @Override

View File

@ -82,7 +82,7 @@ public class RuntimeChildDeclaredExtensionDefinition extends RuntimeChildChoiceD
choiceTypes.add(theChildType); choiceTypes.add(theChildType);
} }
setChoiceTypes(choiceTypes); setChoiceTypes(choiceTypes, Collections.emptyList());
} }
@Override @Override

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 5730
title: "Previously, using the FhirTerser to process an Extension with an Enumeration would fail.
This has been fixed."

View File

@ -58,6 +58,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -1515,7 +1516,24 @@ public class FhirTerserR4Test {
} }
} }
@Test
void extensionWithEnumeration() {
final FhirContext ctx = FhirContext.forR4();
final FhirTerser fhirTerser = ctx.newTerser();
final String url = "http://hl7.org/fhir/StructureDefinition/data-absent-reason]";
final Enumeration<Enumerations.DataAbsentReason> enumerationUnknown = new Enumeration<>(new Enumerations.DataAbsentReasonEnumFactory(), "unknown");
final Extension extensionUnknown = new Extension(url, enumerationUnknown);
final AtomicBoolean result = new AtomicBoolean(false);
final IModelVisitor2 iModelVisitor2 = (theElement, theContainingElementPath, theChildDefinitionPath, theElementDefinitionPath) -> {
result.set(true);
return true;
};
fhirTerser.visit(extensionUnknown, iModelVisitor2);
assertTrue(result.get());
}
private List<String> toStrings(List<StringType> theStrings) { private List<String> toStrings(List<StringType> theStrings) {
ArrayList<String> retVal = new ArrayList<>(); ArrayList<String> retVal = new ArrayList<>();

View File

@ -120,7 +120,7 @@ class ValidatorWrapper {
try { try {
v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager); v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager);
} catch (Exception e) { } catch (Exception e) {
throw new ConfigurationException(Msg.code(648) + e); throw new ConfigurationException(Msg.code(648) + e.getMessage(), e);
} }
v.setAssumeValidRestReferences(isAssumeValidRestReferences()); v.setAssumeValidRestReferences(isAssumeValidRestReferences());