diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java index b8bfba56910..d282e9eb2c8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildAny.java @@ -30,7 +30,6 @@ import org.hl7.fhir.instance.model.api.IBaseReference; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -46,13 +45,15 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition { void sealAndInitialize( FhirContext theContext, Map, BaseRuntimeElementDefinition> theClassToElementDefinitions) { - List> choiceTypes = new ArrayList>(); + List> choiceTypes = new ArrayList<>(); + List> specializationChoiceTypes = new ArrayList<>(); for (Class next : theClassToElementDefinitions.keySet()) { if (next.equals(XhtmlDt.class)) { continue; } + boolean isSpecialization = false; BaseRuntimeElementDefinition nextDef = theClassToElementDefinitions.get(next); if (nextDef instanceof IRuntimeDatatypeDefinition) { 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 * we'll already have CodeDt as an option */ - continue; + isSpecialization = true; } } @@ -68,28 +69,36 @@ public class RuntimeChildAny extends RuntimeChildChoiceDefinition { || IDatatype.class.isAssignableFrom(next) || IBaseDatatype.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) { - choiceTypes.add(next); - } - } - Collections.sort(choiceTypes, new Comparator>() { - @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; + if (isSpecialization) { + specializationChoiceTypes.add(next); } else { - return 1; + choiceTypes.add(next); } } - }); + } - setChoiceTypes(choiceTypes); + choiceTypes.sort(new ResourceTypeNameComparator()); + specializationChoiceTypes.sort(new ResourceTypeNameComparator()); + + setChoiceTypes(choiceTypes, specializationChoiceTypes); super.sealAndInitialize(theContext, theClassToElementDefinitions); } + + private static class ResourceTypeNameComparator implements Comparator> { + @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; + } + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java index f251a2bb878..3657983aad7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildChoiceDefinition.java @@ -22,7 +22,9 @@ package ca.uhn.fhir.context; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Description; +import jakarta.annotation.Nonnull; 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.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseReference; @@ -45,6 +47,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini private Map, BaseRuntimeElementDefinition> myDatatypeToElementDefinition; private String myReferenceSuffix; private List> myResourceTypes; + private List> mySpecializationChoiceTypes = Collections.emptyList(); /** * Constructor @@ -70,8 +73,13 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); } - void setChoiceTypes(List> theChoiceTypes) { + void setChoiceTypes( + @Nonnull List> theChoiceTypes, + @Nonnull List> theSpecializationChoiceTypes) { + Validate.notNull(theChoiceTypes, "theChoiceTypes must not be null"); + Validate.notNull(theSpecializationChoiceTypes, "theSpecializationChoiceTypes must not be null"); myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); + mySpecializationChoiceTypes = Collections.unmodifiableList(theSpecializationChoiceTypes); } public List> getChoices() { @@ -96,14 +104,28 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini void sealAndInitialize( FhirContext theContext, Map, BaseRuntimeElementDefinition> theClassToElementDefinitions) { - myNameToChildDefinition = new HashMap>(); - myDatatypeToElementName = new HashMap, String>(); - myDatatypeToElementDefinition = new HashMap, BaseRuntimeElementDefinition>(); - myResourceTypes = new ArrayList>(); + myNameToChildDefinition = new HashMap<>(); + myDatatypeToElementName = new HashMap<>(); + myDatatypeToElementDefinition = new HashMap<>(); + myResourceTypes = new ArrayList<>(); myReferenceSuffix = "Reference"; - for (Class 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, BaseRuntimeElementDefinition> theClassToElementDefinitions, + List> choiceTypes, + boolean theIsSpecilization) { + for (Class next : choiceTypes) { String elementName = null; BaseRuntimeElementDefinition nextDef; @@ -112,8 +134,10 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini elementName = getElementName() + StringUtils.capitalize(next.getSimpleName()); nextDef = findResourceReferenceDefinition(theClassToElementDefinitions); - myNameToChildDefinition.put(getElementName() + "Reference", nextDef); - myNameToChildDefinition.put(getElementName() + "Resource", nextDef); + if (!theIsSpecilization) { + myNameToChildDefinition.put(getElementName() + "Reference", nextDef); + myNameToChildDefinition.put(getElementName() + "Resource", nextDef); + } myResourceTypes.add((Class) next); @@ -147,21 +171,23 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini } // I don't see how elementName could be null here, but eclipse complains.. - if (elementName != null) { - if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) { + if (!theIsSpecilization) { + 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); } } - /* - * 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); if (myDatatypeToElementName.containsKey(next)) { @@ -175,11 +201,6 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini myDatatypeToElementName.put(next, elementName); } } - - myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition); - myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName); - myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition); - myResourceTypes = Collections.unmodifiableList(myResourceTypes); } public List> getResourceTypes() { @@ -188,8 +209,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini @Override public String getChildNameByDatatype(Class theDatatype) { - String retVal = myDatatypeToElementName.get(theDatatype); - return retVal; + return myDatatypeToElementName.get(theDatatype); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java index 09b8394c43c..4299f364d5c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeChildDeclaredExtensionDefinition.java @@ -82,7 +82,7 @@ public class RuntimeChildDeclaredExtensionDefinition extends RuntimeChildChoiceD choiceTypes.add(theChildType); } - setChoiceTypes(choiceTypes); + setChoiceTypes(choiceTypes, Collections.emptyList()); } @Override diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/5730-fhir-terser-extension-enumeration.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/5730-fhir-terser-extension-enumeration.yaml new file mode 100644 index 00000000000..b5e233c6364 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/5730-fhir-terser-extension-enumeration.yaml @@ -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." diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java index 42ee74b22fb..2dd98b6808c 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java @@ -58,6 +58,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; 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 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 toStrings(List theStrings) { ArrayList retVal = new ArrayList<>(); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java index f8e0c9bda89..c946e2157a7 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java @@ -120,7 +120,7 @@ class ValidatorWrapper { try { v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager); } catch (Exception e) { - throw new ConfigurationException(Msg.code(648) + e); + throw new ConfigurationException(Msg.code(648) + e.getMessage(), e); } v.setAssumeValidRestReferences(isAssumeValidRestReferences());