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