Add ValueSet expansion support, fix profile bugs

HapiWorkerContext needed ValueSet expansion support for validation, now that resources are found and loaded.
ParserBase had a bug when comparing names (was not looking at IdPart)
Profiles had errors in FHIRPaths using invalid $context name
This commit is contained in:
Michael Lawley 2017-03-13 10:10:04 +10:00
parent 3623bbca16
commit 884bfbeed5
4 changed files with 47 additions and 25 deletions

View File

@ -31,13 +31,18 @@ import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpanderFactory;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.dstu2016may.utils.IWorkerContext; import org.hl7.fhir.dstu2016may.utils.IWorkerContext;
import org.hl7.fhir.dstu2016may.validation.IResourceValidator; import org.hl7.fhir.dstu2016may.validation.IResourceValidator;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public final class HapiWorkerContext implements IWorkerContext { public final class HapiWorkerContext implements IWorkerContext, ValueSetExpanderFactory {
private final FhirContext myCtx; private final FhirContext myCtx;
private Map<String, Resource> myFetchedResourceCache = new HashMap<String, Resource>(); private Map<String, Resource> myFetchedResourceCache = new HashMap<String, Resource>();
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@ -259,10 +264,27 @@ public final class HapiWorkerContext implements IWorkerContext {
return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]"); return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]");
} }
@Override
public ValueSetExpander getExpander() {
ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this, this);
return retVal;
}
@Override @Override
public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk) { public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk) {
throw new UnsupportedOperationException(); ValueSetExpansionOutcome vso;
try {
vso = getExpander().expand(theSource);
} catch (InvalidRequestException e) {
throw e;
} catch (Exception e) {
throw new InternalErrorException(e);
}
if (vso.getError() != null) {
throw new InvalidRequestException(vso.getError());
} else {
return vso;
}
} }
@Override @Override

View File

@ -74,7 +74,7 @@ public abstract class ParserBase {
return null; return null;
} }
for (StructureDefinition sd : context.allStructures()) { for (StructureDefinition sd : context.allStructures()) {
if (name.equals(sd.getId())) { if (name.equals(sd.getIdElement().getIdPart())) {
if((ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) if((ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
return sd; return sd;
String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
@ -92,7 +92,7 @@ public abstract class ParserBase {
return null; return null;
} }
for (StructureDefinition sd : context.allStructures()) { for (StructureDefinition sd : context.allStructures()) {
if (name.equals(sd.getId())) { if (name.equals(sd.getIdElement().getIdPart())) {
return sd; return sd;
} }
} }

View File

@ -20460,7 +20460,7 @@
<key value="dom-3"/> <key value="dom-3"/>
<severity value="error"/> <severity value="error"/>
<human value="If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource"/> <human value="If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource"/>
<expression value="contained.select(('#'+id in $context.descendents().reference).not()).empty()"/> <expression value="contained.select(('#'+id in %resource.descendents().reference).not()).empty()"/>
<xpath value="not(exists(for $id in f:contained/*/@id return $id[not(ancestor::f:contained/parent::*/descendant::f:reference/@value=concat('#', $id))]))"/> <xpath value="not(exists(for $id in f:contained/*/@id return $id[not(ancestor::f:contained/parent::*/descendant::f:reference/@value=concat('#', $id))]))"/>
</constraint> </constraint>
<constraint> <constraint>
@ -20649,7 +20649,7 @@
<key value="dom-3"/> <key value="dom-3"/>
<severity value="error"/> <severity value="error"/>
<human value="If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource"/> <human value="If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource"/>
<expression value="contained.select(('#'+id in $context.descendents().reference).not()).empty()"/> <expression value="contained.select(('#'+id in %resource.descendents().reference).not()).empty()"/>
<xpath value="not(exists(for $id in f:contained/*/@id return $id[not(ancestor::f:contained/parent::*/descendant::f:reference/@value=concat('#', $id))]))"/> <xpath value="not(exists(for $id in f:contained/*/@id return $id[not(ancestor::f:contained/parent::*/descendant::f:reference/@value=concat('#', $id))]))"/>
</constraint> </constraint>
<constraint> <constraint>
@ -121071,7 +121071,7 @@
<key value="obs-7"/> <key value="obs-7"/>
<severity value="error"/> <severity value="error"/>
<human value="Component code SHALL not be same as observation code"/> <human value="Component code SHALL not be same as observation code"/>
<expression value="component.where(code = $context.code).empty()"/> <expression value="component.where(code = %resource.code).empty()"/>
<xpath value="not(exists(f:component/f:code)) or count(for $coding in f:code/f:coding return parent::*/f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value and f:system/@value=$coding/f:system/@value])=0"/> <xpath value="not(exists(f:component/f:code)) or count(for $coding in f:code/f:coding return parent::*/f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value and f:system/@value=$coding/f:system/@value])=0"/>
</constraint> </constraint>
<mapping> <mapping>
@ -122307,7 +122307,7 @@
<key value="obs-7"/> <key value="obs-7"/>
<severity value="error"/> <severity value="error"/>
<human value="Component code SHALL not be same as observation code"/> <human value="Component code SHALL not be same as observation code"/>
<expression value="component.where(code = $context.code).empty()"/> <expression value="component.where(code = %resource.code).empty()"/>
<xpath value="not(exists(f:component/f:code)) or count(for $coding in f:code/f:coding return parent::*/f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value and f:system/@value=$coding/f:system/@value])=0"/> <xpath value="not(exists(f:component/f:code)) or count(for $coding in f:code/f:coding return parent::*/f:component/f:code/f:coding[f:code/@value=$coding/f:code/@value and f:system/@value=$coding/f:system/@value])=0"/>
</constraint> </constraint>
<mapping> <mapping>
@ -154515,7 +154515,7 @@
<key value="sdf-8"/> <key value="sdf-8"/>
<severity value="error"/> <severity value="error"/>
<human value="In any snapshot or differential, all the elements except the first have to have a path that starts with the path of the first + &quot;.&quot;"/> <human value="In any snapshot or differential, all the elements except the first have to have a path that starts with the path of the first + &quot;.&quot;"/>
<expression value="snapshot.element.tail().all(path.startsWith($context.snapshot.element.first().path+'.')) and differential.element.tail().all(path.startsWith($context.differential.element.first().path+'.'))"/> <expression value="snapshot.element.tail().all(path.startsWith(%resource.snapshot.element.first().path+'.')) and differential.element.tail().all(path.startsWith(%resource.differential.element.first().path+'.'))"/>
<xpath value="string-join(for $elementName in f:*[self::f:snapshot or self::f:differential]/f:element[position()>1]/f:path/@value return if (starts-with($elementName, concat($elementName/ancestor::f:element/parent::f:*/f:element[1]/f:path/@value, '.'))) then '' else $elementName,'')=''"/> <xpath value="string-join(for $elementName in f:*[self::f:snapshot or self::f:differential]/f:element[position()>1]/f:path/@value return if (starts-with($elementName, concat($elementName/ancestor::f:element/parent::f:*/f:element[1]/f:path/@value, '.'))) then '' else $elementName,'')=''"/>
</constraint> </constraint>
<constraint> <constraint>
@ -155580,7 +155580,7 @@
<key value="sdf-8"/> <key value="sdf-8"/>
<severity value="error"/> <severity value="error"/>
<human value="In any snapshot or differential, all the elements except the first have to have a path that starts with the path of the first + &quot;.&quot;"/> <human value="In any snapshot or differential, all the elements except the first have to have a path that starts with the path of the first + &quot;.&quot;"/>
<expression value="snapshot.element.tail().all(path.startsWith($context.snapshot.element.first().path+'.')) and differential.element.tail().all(path.startsWith($context.differential.element.first().path+'.'))"/> <expression value="snapshot.element.tail().all(path.startsWith(%resource.snapshot.element.first().path+'.')) and differential.element.tail().all(path.startsWith(%resource.differential.element.first().path+'.'))"/>
<xpath value="string-join(for $elementName in f:*[self::f:snapshot or self::f:differential]/f:element[position()>1]/f:path/@value return if (starts-with($elementName, concat($elementName/ancestor::f:element/parent::f:*/f:element[1]/f:path/@value, '.'))) then '' else $elementName,'')=''"/> <xpath value="string-join(for $elementName in f:*[self::f:snapshot or self::f:differential]/f:element[position()>1]/f:path/@value return if (starts-with($elementName, concat($elementName/ancestor::f:element/parent::f:*/f:element[1]/f:path/@value, '.'))) then '' else $elementName,'')=''"/>
</constraint> </constraint>
<constraint> <constraint>

View File

@ -4681,7 +4681,7 @@
<key value="ref-1"/> <key value="ref-1"/>
<severity value="error"/> <severity value="error"/>
<human value="SHALL have a local reference if the resource is provided inline"/> <human value="SHALL have a local reference if the resource is provided inline"/>
<expression value="reference.startsWith('#').not() or ($context.reference.substring(1).trace('url') in $resource.contained.id.trace('ids'))"/> <expression value="reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))"/>
<xpath value="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])"/> <xpath value="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])"/>
</constraint> </constraint>
<mapping> <mapping>
@ -4779,7 +4779,7 @@
<key value="ref-1"/> <key value="ref-1"/>
<severity value="error"/> <severity value="error"/>
<human value="SHALL have a local reference if the resource is provided inline"/> <human value="SHALL have a local reference if the resource is provided inline"/>
<expression value="reference.startsWith('#').not() or ($context.reference.substring(1).trace('url') in $resource.contained.id.trace('ids'))"/> <expression value="reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))"/>
<xpath value="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])"/> <xpath value="not(starts-with(f:reference/@value, '#')) or exists(ancestor::*[self::f:entry or self::f:parameter]/f:resource/f:*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')]|/*/f:contained/f:*[f:id/@value=substring-after(current()/f:reference/@value, '#')])"/>
</constraint> </constraint>
<mapping> <mapping>