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.ValueSetExpansionComponent;
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.ValueSetExpanderFactory;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.dstu2016may.utils.IWorkerContext;
import org.hl7.fhir.dstu2016may.validation.IResourceValidator;
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 Map<String, Resource> myFetchedResourceCache = new HashMap<String, Resource>();
private IValidationSupport myValidationSupport;
@ -201,8 +206,8 @@ public final class HapiWorkerContext implements IWorkerContext {
}
}
}
boolean caseSensitive = true;
if (isNotBlank(theSystem)) {
CodeSystem system = fetchCodeSystem(theSystem);
@ -259,10 +264,27 @@ public final class HapiWorkerContext implements IWorkerContext {
return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]");
}
@Override
public ValueSetExpander getExpander() {
ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this, this);
return retVal;
}
@Override
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

View File

@ -25,13 +25,13 @@ import org.hl7.fhir.utilities.Utilities;
public abstract class ParserBase {
interface IErrorNotifier {
}
public enum ValidationPolicy { NONE, QUICK, EVERYTHING }
public static boolean isPrimitive(String code) {
return Utilities.existsInList(code,
"xhtml", "boolean", "integer", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime",
return Utilities.existsInList(code,
"xhtml", "boolean", "integer", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime",
"time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "xhtml", "base64Binary");
}
@ -49,12 +49,12 @@ public abstract class ParserBase {
this.policy = policy;
this.errors = errors;
}
public abstract Element parse(InputStream stream) throws Exception;
public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws Exception;
public void logError(int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError {
if (policy == ValidationPolicy.EVERYTHING) {
ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level);
@ -62,8 +62,8 @@ public abstract class ParserBase {
} else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK))
throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col));
}
protected StructureDefinition getDefinition(int line, int col, String ns, String name) throws FHIRFormatError {
if (ns == null) {
logError(line, col, name, IssueType.STRUCTURE, "This cannot be parsed as a FHIR object (no namespace)", IssueSeverity.FATAL);
@ -74,7 +74,7 @@ public abstract class ParserBase {
return null;
}
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"))
return sd;
String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
@ -92,7 +92,7 @@ public abstract class ParserBase {
return null;
}
for (StructureDefinition sd : context.allStructures()) {
if (name.equals(sd.getId())) {
if (name.equals(sd.getIdElement().getIdPart())) {
return sd;
}
}
@ -100,7 +100,7 @@ public abstract class ParserBase {
return null;
}
protected List<Property> getChildProperties(Property property, String elementName, String statedType) throws DefinitionException {
ElementDefinition ed = property.getDefinition();
StructureDefinition sd = property.getStructure();
@ -128,12 +128,12 @@ public abstract class ParserBase {
if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaultype"))
t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaultype");
boolean ok = false;
for (TypeRefComponent tr : ed.getType())
if (tr.getCode().equals(t))
for (TypeRefComponent tr : ed.getType())
if (tr.getCode().equals(t))
ok = true;
if (!ok)
throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+property.getDefinition().getPath());
} else {
t = elementName.substring(tail(ed.getPath()).length() - 3);
if (isPrimitive(lowFirst(t)))

View File

@ -20460,7 +20460,7 @@
<key value="dom-3"/>
<severity value="error"/>
<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))]))"/>
</constraint>
<constraint>
@ -20649,7 +20649,7 @@
<key value="dom-3"/>
<severity value="error"/>
<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))]))"/>
</constraint>
<constraint>
@ -121071,7 +121071,7 @@
<key value="obs-7"/>
<severity value="error"/>
<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"/>
</constraint>
<mapping>
@ -122307,7 +122307,7 @@
<key value="obs-7"/>
<severity value="error"/>
<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"/>
</constraint>
<mapping>
@ -154515,7 +154515,7 @@
<key value="sdf-8"/>
<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;"/>
<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,'')=''"/>
</constraint>
<constraint>
@ -155580,7 +155580,7 @@
<key value="sdf-8"/>
<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;"/>
<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,'')=''"/>
</constraint>
<constraint>

View File

@ -4681,7 +4681,7 @@
<key value="ref-1"/>
<severity value="error"/>
<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, '#')])"/>
</constraint>
<mapping>
@ -4779,7 +4779,7 @@
<key value="ref-1"/>
<severity value="error"/>
<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, '#')])"/>
</constraint>
<mapping>