Merge pull request #679 from hapifhir/gg-202111-r4B-related

Gg 202111 r4 b related
This commit is contained in:
Grahame Grieve 2021-12-01 05:25:56 +11:00 committed by GitHub
commit 13073a189d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 369 additions and 131 deletions

View File

@ -54,7 +54,7 @@ public abstract class BaseLoaderR5 implements IContextResourceLoader {
if (VersionUtilities.isR5Ver(npm.fhirVersion())) { if (VersionUtilities.isR5Ver(npm.fhirVersion())) {
return new R5ToR5Loader(types, lkp.forNewPackage(npm)); return new R5ToR5Loader(types, lkp.forNewPackage(npm));
} else if (VersionUtilities.isR4Ver(npm.fhirVersion())) { } else if (VersionUtilities.isR4Ver(npm.fhirVersion())) {
return new R4ToR5Loader(types, lkp.forNewPackage(npm)); return new R4ToR5Loader(types, lkp.forNewPackage(npm), npm.version());
} else if (VersionUtilities.isR3Ver(npm.fhirVersion())) { } else if (VersionUtilities.isR3Ver(npm.fhirVersion())) {
return new R3ToR5Loader(types, lkp.forNewPackage(npm)); return new R3ToR5Loader(types, lkp.forNewPackage(npm));
} else if (VersionUtilities.isR2Ver(npm.fhirVersion())) { } else if (VersionUtilities.isR2Ver(npm.fhirVersion())) {

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.convertors.loaders.loaderR5; package org.hl7.fhir.convertors.loaders.loaderR5;
import org.apache.http.auth.AuthScheme;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
@ -36,12 +38,14 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.formats.JsonParser; import org.hl7.fhir.r4.formats.JsonParser;
import org.hl7.fhir.r4.formats.XmlParser; import org.hl7.fhir.r4.formats.XmlParser;
import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r5.conformance.StructureDefinitionHacker;
import org.hl7.fhir.r5.context.IWorkerContext.IContextResourceLoader; import org.hl7.fhir.r5.context.IWorkerContext.IContextResourceLoader;
import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.Bundle.BundleType; import org.hl7.fhir.r5.model.Bundle.BundleType;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.utilities.VersionUtilities;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -52,9 +56,11 @@ import java.util.UUID;
public class R4ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader { public class R4ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader {
private final BaseAdvisor_40_50 advisor = new BaseAdvisor_40_50(); private final BaseAdvisor_40_50 advisor = new BaseAdvisor_40_50();
private String version;
public R4ToR5Loader(String[] types, ILoaderKnowledgeProviderR5 lkp) { public R4ToR5Loader(String[] types, ILoaderKnowledgeProviderR5 lkp, String version) { // might be 4B
super(types, lkp); super(types, lkp);
this.version = version;
} }
@Override @Override
@ -123,6 +129,9 @@ public class R4ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader
if (killPrimitives) { if (killPrimitives) {
throw new FHIRException("Cannot kill primitives when using deferred loading"); throw new FHIRException("Cannot kill primitives when using deferred loading");
} }
if (r5 instanceof StructureDefinition && VersionUtilities.isR4BVer(version)) {
r5 = new StructureDefinitionHacker(version).fixSD((StructureDefinition) r5);
}
if (patchUrls) { if (patchUrls) {
if (r5 instanceof StructureDefinition) { if (r5 instanceof StructureDefinition) {
StructureDefinition sd = (StructureDefinition) r5; StructureDefinition sd = (StructureDefinition) r5;

View File

@ -80,7 +80,7 @@ public class DicomPackageBuilder {
vs.setId(vs.getId().substring(0, 64)); vs.setId(vs.getId().substring(0, 64));
} }
if (ids.contains(vs.getId())) { if (ids.contains(vs.getId())) {
throw new Error("Duplicate Id once Ids cut off at 64 char: "+vs.getId()); throw new Error("Duplicate Id (note Ids cut off at 64 char): "+vs.getId());
} }
ids.add(vs.getId()); ids.add(vs.getId());
gen.addFile(Category.RESOURCE, "ValueSet-"+vs.getId()+".json", new JsonParser().setOutputStyle(OutputStyle.NORMAL).composeBytes(vs)); gen.addFile(Category.RESOURCE, "ValueSet-"+vs.getId()+".json", new JsonParser().setOutputStyle(OutputStyle.NORMAL).composeBytes(vs));
@ -113,7 +113,7 @@ public class DicomPackageBuilder {
private JsonObject buildPackage() { private JsonObject buildPackage() {
JsonObject npm = new JsonObject(); JsonObject npm = new JsonObject();
npm.addProperty("tools-version", 3); npm.addProperty("tools-version", 3);
npm.addProperty("type", "fhir.ig"); npm.addProperty("type", "Conformance");
npm.addProperty("license", "free"); npm.addProperty("license", "free");
npm.addProperty("author", "FHIR Project for DICOM"); npm.addProperty("author", "FHIR Project for DICOM");
npm.addProperty("name", "fhir.dicom"); npm.addProperty("name", "fhir.dicom");

View File

@ -0,0 +1,48 @@
package org.hl7.fhir.r5.conformance;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.VersionUtilities;
public class StructureDefinitionHacker {
private String version;
public StructureDefinitionHacker(String version) {
super();
this.version = version;
}
public Resource fixSD(StructureDefinition sd) {
if (VersionUtilities.isR4BVer(version) && sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type")) {
// the definition of this one is wrong in R4B
return fixR4BFhirType(sd);
}
return sd;
}
private Resource fixR4BFhirType(StructureDefinition sd) {
for (ElementDefinition ed : sd.getDifferential().getElement()) {
if (ed.getPath().equals("Extension.value[x]")) {
fixEDType(ed, "url", "uri");
}
}
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals("Extension.value[x]")) {
fixEDType(ed, "url", "uri");
}
}
return sd;
}
private void fixEDType(ElementDefinition ed, String orig, String repl) {
for (TypeRefComponent t : ed.getType()) {
if (orig.equals(t.getCode())) {
t.setCode(repl);
}
}
}
}

View File

@ -498,6 +498,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
@Override @Override
public CodeSystem fetchCodeSystem(String system) { public CodeSystem fetchCodeSystem(String system) {
if (system == null) {
return null;
}
if (system.contains("|")) {
String s = system.substring(0, system.indexOf("|"));
String v = system.substring(system.indexOf("|")+1);
return fetchCodeSystem(s, v);
}
CodeSystem cs; CodeSystem cs;
synchronized (lock) { synchronized (lock) {
cs = codeSystems.get(system); cs = codeSystems.get(system);
@ -511,6 +519,23 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return cs; return cs;
} }
public CodeSystem fetchCodeSystem(String system, String version) {
if (version == null) {
return fetchCodeSystem(system);
}
CodeSystem cs;
synchronized (lock) {
cs = codeSystems.get(system, version);
}
if (cs == null && locator != null) {
locator.findResource(this, system);
synchronized (lock) {
cs = codeSystems.get(system);
}
}
return cs;
}
@Override @Override
public boolean supportsSystem(String system) throws TerminologyServiceException { public boolean supportsSystem(String system) throws TerminologyServiceException {
synchronized (lock) { synchronized (lock) {

View File

@ -475,6 +475,7 @@ public interface IWorkerContext {
* @return * @return
*/ */
public CodeSystem fetchCodeSystem(String system); public CodeSystem fetchCodeSystem(String system);
public CodeSystem fetchCodeSystem(String system, String version);
/** /**
* True if the underlying terminology service provider will do * True if the underlying terminology service provider will do

View File

@ -118,11 +118,13 @@ public abstract class ParserBase {
//FIXME: i18n should be done here //FIXME: i18n should be done here
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 (errors != null) {
ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level); if (policy == ValidationPolicy.EVERYTHING) {
errors.add(msg); ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level);
} else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK)) errors.add(msg);
throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col)); } else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK))
throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col));
}
} }

View File

@ -237,6 +237,7 @@ public class SHCParser extends ParserBase {
private static final int BUFFER_SIZE = 1024; private static final int BUFFER_SIZE = 1024;
public static final String CURRENT_PACKAGE = "hl7.fhir.uv.shc-vaccination#0.6.2"; public static final String CURRENT_PACKAGE = "hl7.fhir.uv.shc-vaccination#0.6.2";
private static final int MAX_ALLOWED_SHC_LENGTH = 1195;
// todo: deal with chunking // todo: deal with chunking
public static String decodeQRCode(String src) { public static String decodeQRCode(String src) {
@ -253,10 +254,14 @@ public class SHCParser extends ParserBase {
return b.toString(); return b.toString();
} }
public static JWT decodeJWT(String jwt) throws IOException, DataFormatException { public JWT decodeJWT(String jwt) throws IOException, DataFormatException {
if (jwt.startsWith("shc:/")) { if (jwt.startsWith("shc:/")) {
jwt = decodeQRCode(jwt); jwt = decodeQRCode(jwt);
} }
if (jwt.length() > MAX_ALLOWED_SHC_LENGTH) {
logError(-1, -1, "jwt", IssueType.TOOLONG, "JWT Payload limit length is "+MAX_ALLOWED_SHC_LENGTH+" bytes for a single image - this has "+jwt.length()+" bytes", IssueSeverity.ERROR);
}
String[] parts = splitToken(jwt); String[] parts = splitToken(jwt);
byte[] headerJson; byte[] headerJson;
byte[] payloadJson; byte[] payloadJson;

View File

@ -94,8 +94,10 @@ public class CodeSystemRenderer extends TerminologyRenderer {
private void generateProperties(XhtmlNode x, CodeSystem cs) { private void generateProperties(XhtmlNode x, CodeSystem cs) {
if (cs.hasProperty()) { if (cs.hasProperty()) {
boolean hasRendered = false; boolean hasRendered = false;
boolean hasURI = false;
for (PropertyComponent p : cs.getProperty()) { for (PropertyComponent p : cs.getProperty()) {
hasRendered = hasRendered || !p.getCode().equals(ToolingExtensions.getPresentation(p, p.getCodeElement())); hasRendered = hasRendered || !p.getCode().equals(ToolingExtensions.getPresentation(p, p.getCodeElement()));
hasURI = hasURI || p.hasUri();
} }
x.para().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Properties", getContext().getLang())); x.para().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Properties", getContext().getLang()));
@ -105,18 +107,22 @@ public class CodeSystemRenderer extends TerminologyRenderer {
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Name", getContext().getLang())); tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Name", getContext().getLang()));
} }
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Code", getContext().getLang())); tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Code", getContext().getLang()));
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "URL", getContext().getLang())); if (hasURI) {
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Description", getContext().getLang())); tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "URL", getContext().getLang()));
}
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Type", getContext().getLang())); tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Type", getContext().getLang()));
tr.td().b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Description", getContext().getLang()));
for (PropertyComponent p : cs.getProperty()) { for (PropertyComponent p : cs.getProperty()) {
tr = tbl.tr(); tr = tbl.tr();
if (hasRendered) { if (hasRendered) {
tr.td().tx(ToolingExtensions.getPresentation(p, p.getCodeElement())); tr.td().tx(ToolingExtensions.getPresentation(p, p.getCodeElement()));
} }
tr.td().tx(p.getCode()); tr.td().tx(p.getCode());
tr.td().tx(p.getUri()); if (hasURI) {
tr.td().tx(p.getDescription()); tr.td().tx(p.getUri());
}
tr.td().tx(p.hasType() ? p.getType().toCode() : ""); tr.td().tx(p.hasType() ? p.getType().toCode() : "");
tr.td().tx(p.getDescription());
} }
} }
} }
@ -141,6 +147,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
boolean hierarchy = false; boolean hierarchy = false;
boolean version = false; boolean version = false;
boolean ignoreStatus = false; boolean ignoreStatus = false;
boolean isSupplement = cs.getContent() == CodeSystemContentMode.SUPPLEMENT;
List<PropertyComponent> properties = new ArrayList<>(); List<PropertyComponent> properties = new ArrayList<>();
for (PropertyComponent cp : cs.getProperty()) { for (PropertyComponent cp : cs.getProperty()) {
if (showPropertyInTable(cp)) { if (showPropertyInTable(cp)) {
@ -170,7 +177,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
List<String> langs = new ArrayList<>(); List<String> langs = new ArrayList<>();
addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, null, false), maps); addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, definitions, commentS, version, deprecated, properties, null, false), maps);
for (ConceptDefinitionComponent c : csNav.getConcepts(null)) { for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, definitions, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs) || hasExtensions; hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, definitions, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs, isSupplement) || hasExtensions;
} }
if (langs.size() > 0) { if (langs.size() > 0) {
Collections.sort(langs); Collections.sort(langs);
@ -223,10 +230,10 @@ public class CodeSystemRenderer extends TerminologyRenderer {
return true; return true;
} }
String uri = cp.getUri(); String uri = cp.getUri();
String code = null;
if (Utilities.noString(uri)){ if (Utilities.noString(uri)){
return false; return true; // do we always want to render properties in this case? Not sure...
} }
String code = null;
if (uri.contains("#")) { if (uri.contains("#")) {
code = uri.substring(uri.indexOf("#")+1); code = uri.substring(uri.indexOf("#")+1);
uri = uri.substring(0, uri.indexOf("#")); uri = uri.substring(0, uri.indexOf("#"));
@ -290,7 +297,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean hasDefinitions, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs) throws FHIRFormatError, DefinitionException, IOException { private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean hasDefinitions, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, List<String> langs, boolean isSupplement) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false; boolean hasExtensions = false;
XhtmlNode tr = t.tr(); XhtmlNode tr = t.tr();
XhtmlNode td = tr.td(); XhtmlNode td = tr.td();
@ -300,7 +307,12 @@ public class CodeSystemRenderer extends TerminologyRenderer {
String s = Utilities.padLeft("", '\u00A0', level*2); String s = Utilities.padLeft("", '\u00A0', level*2);
td.addText(s); td.addText(s);
} }
td.attribute("style", "white-space:nowrap").addText(c.getCode()); String link = isSupplement ? getLinkForCode(cs.getSupplements(), null, c.getCode()) : null;
if (link != null) {
td.ah(link).attribute("style", "white-space:nowrap").addText(c.getCode());
} else {
td.attribute("style", "white-space:nowrap").addText(c.getCode());
}
XhtmlNode a; XhtmlNode a;
if (c.hasCodeElement()) { if (c.hasCodeElement()) {
td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode())); td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()));
@ -450,7 +462,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
} }
List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c); List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) { for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs) || hasExtensions; hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, hasDefinitions, comment, version, deprecated, maps, system, cs, properties, csNav, langs, isSupplement) || hasExtensions;
} }
for (ConceptDefinitionComponent cc : ocl) { for (ConceptDefinitionComponent cc : ocl) {
tr = t.tr(); tr = t.tr();

View File

@ -711,6 +711,38 @@ public class DataRenderer extends Renderer {
} }
} }
protected String getLinkForCode(String system, String version, String code) {
if ("http://snomed.info/sct".equals(system)) {
if (!Utilities.noString(code)) {
return "http://snomed.info/id/"+code;
} else {
return "https://browser.ihtsdotools.org/";
}
} else if ("http://loinc.org".equals(system)) {
if (!Utilities.noString(code)) {
return "https://loinc.org/"+code;
} else {
return "https://loinc.org/";
}
} else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) {
if (!Utilities.noString(code)) {
return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code;
} else {
return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html";
}
} else {
CodeSystem cs = context.getWorker().fetchCodeSystem(system, version);
if (cs != null && cs.hasUserData("path")) {
if (!Utilities.noString(code)) {
return cs.getUserString("path")+"#"+Utilities.nmtokenize(code);
} else {
return cs.getUserString("path");
}
}
}
return null;
}
protected void renderCodingWithDetails(XhtmlNode x, Coding c) { protected void renderCodingWithDetails(XhtmlNode x, Coding c) {
String s = ""; String s = "";
if (c.hasDisplayElement()) if (c.hasDisplayElement())
@ -720,22 +752,13 @@ public class DataRenderer extends Renderer {
String sn = describeSystem(c.getSystem()); String sn = describeSystem(c.getSystem());
if ("http://snomed.info/sct".equals(c.getSystem())) { String link = getLinkForCode(c.getSystem(), c.getVersion(), c.getCode());
if (c.hasCode()) { if (link != null) {
x.ah("http://snomed.info/id/"+c.getCode()).tx(sn); x.ah(link).tx(sn);
} else {
x.ah("https://browser.ihtsdotools.org/").tx(sn);
}
} else if ("http://loinc.org".equals(c.getSystem())) {
x.ah("https://loinc.org/").tx(sn);
} else { } else {
CodeSystem cs = context.getWorker().fetchCodeSystem(c.getSystem()); x.tx(sn);
if (cs != null && cs.hasUserData("path")) {
x.ah(cs.getUserString("path")).tx(sn);
} else {
x.tx(sn);
}
} }
x.tx(" "); x.tx(" ");
x.tx(c.getCode()); x.tx(c.getCode());
if (!Utilities.noString(s)) { if (!Utilities.noString(s)) {

View File

@ -1,8 +1,15 @@
package org.hl7.fhir.r5.utils.validation; package org.hl7.fhir.r5.utils.validation;
import java.util.List;
import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
public interface IValidationPolicyAdvisor { public interface IValidationPolicyAdvisor {
@ -38,4 +45,38 @@ public interface IValidationPolicyAdvisor {
String path, String path,
String url); String url);
/**
* Called before validating a concept in an instance against the terminology sub-system
*
* There's two reasons to use this policy advisor feature:
* - save time by not calling the terminology server for validation that don't bring value to the context calling the validation
* - suppressing known issues from being listed as a problem
*
* Note that the terminology subsystem has two parts: a mini-terminology server running inside the
* validator, and then calling out to an external terminology service (usually tx.fhir.org, though you
* run your own local copy of this - see https://confluence.hl7.org/display/FHIR/Running+your+own+copy+of+tx.fhir.org).
* You can't tell which subsystem will handle the terminology validation directly from the content provided here which
* subsystem will be called - you'll haev to investigate based on your set up. (matters, since it makes a huge performance
* difference, though it also depends on caching, and the impact of caching is also not known at this point)
*
* @param validator
* @param appContext What was originally provided from the app for it's context
* @param stackPath The current path for the stack. Note that the because of cross-references and FHIRPath conformsTo() statements, the stack can wind through the content unpredictably.
* @param definition the definition being validated against (might be useful: ElementDefinition.base.path, ElementDefinition.type, ElementDefinition.binding
* @param structure The structure definition that contains the element definition being validated against (may be from the base spec, may be from a profile)
* @param kind The part of the binding being validated
* @param valueSet The value set for the binding part that's being validated
* @param systems A list of canonical URls (including versions if known) of the systems in the instance that's being validated. Note that if a plain code is being validated, then there'll be no known system when this is called (systems will be empty, not null)
* @return {@link CodedContentValidationPolicy}
*/
CodedContentValidationPolicy policyForCodedContent(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
BindingKind kind,
ValueSet valueSet,
List<String> systems);
} }

View File

@ -0,0 +1,14 @@
package org.hl7.fhir.r5.utils.validation.constants;
public enum BindingKind {
/**
* The primary binding e.g. ElementDefinition.binding.valueSet
*/
PRIMARY,
/**
* The max value set
*/
MAX_VS;
}

View File

@ -0,0 +1,21 @@
package org.hl7.fhir.r5.utils.validation.constants;
public enum CodedContentValidationPolicy {
/**
* don't validate the code
*/
IGNORE,
/**
* validate the code against the underlying code systems
*/
CODE,
/**
* validate the code against the value set too.
* Note that this isn't much faster than just validating the code since
* the expensive part is hitting the terminology server (if necessary)
* and that has to be done for the code part too
*/
VALUESET //
}

View File

@ -15,6 +15,7 @@ import org.apache.commons.lang3.NotImplementedException;
import org.fhir.ucum.UcumException; import org.fhir.ucum.UcumException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.BooleanType;
@ -187,7 +188,11 @@ public class FHIRPathTests {
} else { } else {
res = resources.get(input); res = resources.get(input);
if (res == null) { if (res == null) {
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input)); if (input.endsWith(".json")) {
res = new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
} else {
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
}
resources.put(input, res); resources.put(input, res);
} }
fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node); fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node);

View File

@ -23,6 +23,7 @@ import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xhtml.XhtmlNode;
@ -55,63 +56,59 @@ public class NarrativeGeneratorTests {
} }
private void checkDateTimeRendering(String src, String lang, String country, ZoneId tz, FormatStyle fmt, ResourceRendererMode mode, String expected) throws FHIRFormatError, DefinitionException, IOException { private void checkDateTimeRendering(String src, String lang, String country, ZoneId tz, String fmt, ResourceRendererMode mode,
String... expected) throws FHIRFormatError, DefinitionException, IOException {
rc.setLocale(new java.util.Locale(lang, country)); rc.setLocale(new java.util.Locale(lang, country));
rc.setTimeZoneId(tz); rc.setTimeZoneId(tz);
if (fmt == null) { if (fmt == null) {
rc.setDateTimeFormat(null); rc.setDateTimeFormat(null);
rc.setDateFormat(null); rc.setDateFormat(null);
} else { } else {
rc.setDateTimeFormat(DateTimeFormatter.ofLocalizedDateTime(fmt).withLocale(rc.getLocale())); // really, it would be better to test patterns based on FormatStyle here, since
rc.setDateFormat(DateTimeFormatter.ofLocalizedDate(fmt).withLocale(rc.getLocale())); // that's what will be used in the real world, but
rc.setDateTimeFormat(DateTimeFormatter.ofPattern(fmt));
rc.setDateFormat(DateTimeFormatter.ofPattern(fmt));
} }
rc.setMode(mode); rc.setMode(mode);
DateTimeType dt = new DateTimeType(src); DateTimeType dt = new DateTimeType(src);
String actual = new DataRenderer(rc).display(dt); String actual = new DataRenderer(rc).display(dt);
Assert.assertEquals(expected, actual);
Assert.assertTrue("Actual = "+actual+", expected one of "+expected, Utilities.existsInList(actual, expected));
XhtmlNode node = new XhtmlNode(NodeType.Element, "p"); XhtmlNode node = new XhtmlNode(NodeType.Element, "p");
new DataRenderer(rc).render(node, dt); new DataRenderer(rc).render(node, dt);
actual = new XhtmlComposer(true, false).compose(node); actual = new XhtmlComposer(true, false).compose(node);
Assert.assertEquals("<p>"+expected+"</p>", actual); Assert.assertTrue(actual.startsWith("<p>"));
Assert.assertTrue(actual.endsWith("</p>"));
Assert.assertTrue("Actual = "+actual+", expected one of "+expected, Utilities.existsInList(actual.substring(0, actual.length()-4).substring(3), expected));
}
@Test
public void testDateTimeRendering1() throws FHIRFormatError, DefinitionException, IOException {
checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.TECHNICAL, "2021-11-19T14:13:12Z");
} }
// @Test
// public void testDateTimeLocaleConsistency() throws FHIRFormatError, DefinitionException, IOException { @Test
// Locale locale = new java.util.Locale("en", "AU"); public void testDateTimeRendering2() throws FHIRFormatError, DefinitionException, IOException {
// DateTimeFormatter fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(locale); checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("Australia/Sydney"), null, ResourceRendererMode.TECHNICAL, "2021-11-20T01:13:12+11:00");
// ZonedDateTime zdt = ZonedDateTime.parse("2021-11-19T14:13:12Z"); }
// Assert.assertEquals("19 Nov. 2021, 2:13:12 pm", fmt.format(zdt));
// } @Test
// public void testDateTimeRendering3() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), "yyyy/MM/dd hh:mm:ss", ResourceRendererMode.TECHNICAL, "2021/11/19 02:13:12");
// @Test }
// public void testDateTimeRendering1() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.TECHNICAL, "2021-11-19T14:13:12Z"); @Test // varies between versions, so multiple possible expected
// } public void testDateTimeRendering4() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.END_USER, "19/11/21, 2:13 pm", "19/11/21 2:13 PM");
// }
// @Test
// public void testDateTimeRendering2() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("Australia/Sydney"), null, ResourceRendererMode.TECHNICAL, "2021-11-20T01:13:12+11:00"); @Test
// } public void testDateTimeRendering5() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.END_USER, "19/11/21");
// @Test }
// public void testDateTimeRendering3() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), FormatStyle.SHORT, ResourceRendererMode.TECHNICAL, "19/11/21, 2:13 pm");
// }
//
//
// @Test
// public void testDateTimeRendering4() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.END_USER, "19/11/21, 2:13 pm");
// }
//
//
// @Test
// public void testDateTimeRendering5() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.END_USER, "19/11/21");
// }
//
} }

View File

@ -1510,5 +1510,9 @@ public class Utilities {
return -1; return -1;
} }
public static String toString(String[] expected) {
return "['"+String.join("' | '", expected)+"']";
}
} }

View File

@ -99,7 +99,7 @@ public class ResourceChecker {
String s = new String(cnt, StandardCharsets.UTF_8); String s = new String(cnt, StandardCharsets.UTF_8);
if (s.startsWith("shc:/")) if (s.startsWith("shc:/"))
s = SHCParser.decodeQRCode(s); s = SHCParser.decodeQRCode(s);
JWT jwt = SHCParser.decodeJWT(s); JWT jwt = new SHCParser(context).decodeJWT(s);
return Manager.FhirFormat.SHC; return Manager.FhirFormat.SHC;
} catch (Exception e) { } catch (Exception e) {
if (debug) { if (debug) {

View File

@ -752,17 +752,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
Element.SpecialElement containingResourceType, Element.SpecialElement containingResourceType,
String path, String path,
String url) { String url) {
Resource resource = context.fetchResource(StructureDefinition.class, url); return ContainedReferenceValidationPolicy.CHECK_VALID;
if (resource != null) { }
return ContainedReferenceValidationPolicy.CHECK_VALID;
} @Override
if (!(url.contains("hl7.org") || url.contains("fhir.org"))) { public CodedContentValidationPolicy policyForCodedContent(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind, ValueSet valueSet, List<String> systems) {
return ContainedReferenceValidationPolicy.IGNORE; return CodedContentValidationPolicy.VALUESET;
} else if (policyAdvisor != null) {
return policyAdvisor.policyForContained(validator, appContext, containerType, containerId, containingResourceType, path, url);
} else {
return ContainedReferenceValidationPolicy.CHECK_TYPE;
}
} }
@Override @Override
@ -825,4 +820,5 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
public boolean fetchesCanonicalResource(IResourceValidator validator, String url) { public boolean fetchesCanonicalResource(IResourceValidator validator, String url) {
return fetcher != null && fetcher.fetchesCanonicalResource(validator, url); return fetcher != null && fetcher.fetchesCanonicalResource(validator, url);
} }
} }

View File

@ -48,7 +48,7 @@ public class ValidatorUtils {
if (version.startsWith("3.0")) if (version.startsWith("3.0"))
return new R3ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5()); return new R3ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5());
if (version.startsWith("4.0")) if (version.startsWith("4.0"))
return new R4ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5()); return new R4ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5(), version);
if (version.startsWith("5.0")) if (version.startsWith("5.0"))
return new R5ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5()); return new R5ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5());
return null; return null;

View File

@ -7,10 +7,15 @@ import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ICanonicalResourceLocator; import org.hl7.fhir.r5.context.IWorkerContext.ICanonicalResourceLocator;
import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.TerminologyClient; import org.hl7.fhir.r5.terminologies.TerminologyClient;
import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
@ -68,7 +73,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
Element.SpecialElement containingResourceType, Element.SpecialElement containingResourceType,
String path, String path,
String url) { String url) {
return ContainedReferenceValidationPolicy.CHECK_TYPE; return ContainedReferenceValidationPolicy.CHECK_VALID;
} }
@Override @Override
@ -276,4 +281,10 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
} }
} }
@Override
public CodedContentValidationPolicy policyForCodedContent(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition,
StructureDefinition structure, BindingKind kind, ValueSet valueSet, List<String> systems) {
return CodedContentValidationPolicy.VALUESET;
}
} }

View File

@ -302,7 +302,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} else if (item instanceof Element) { } else if (item instanceof Element) {
Element e = (Element) item; Element e = (Element) item;
if (e.getName().equals("contained")) { if (e.getSpecial() != null) {
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
} else { } else {
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
@ -2042,7 +2042,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern); checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern);
} }
private void checkPrimitive(Object appContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException { private void checkPrimitive(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException {
if (isBlank(e.primitiveValue())) { if (isBlank(e.primitiveValue())) {
if (e.primitiveValue() == null) if (e.primitiveValue() == null)
rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT); rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT);
@ -2117,13 +2117,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean found; boolean found;
try { try {
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) ||
SpecialExtensions.isKnownExtension(url) || isXverUrl(url) || fetcher.resolveURL(this, appContext, path, url, type); SpecialExtensions.isKnownExtension(url) || isXverUrl(url) || fetcher.resolveURL(this, hostContext, path, url, type);
} catch (IOException e1) { } catch (IOException e1) {
found = false; found = false;
} }
if (!found) { if (!found) {
if (type.equals("canonical")) { if (type.equals("canonical")) {
ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, appContext, path, url); ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url);
if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) { if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url);
} else { } else {
@ -2140,7 +2140,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} else { } else {
if (type.equals("canonical")) { if (type.equals("canonical")) {
ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, appContext, path, url); ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url);
if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) { if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) {
try { try {
Resource r = fetcher.fetchCanonicalResource(this, url); Resource r = fetcher.fetchCanonicalResource(this, url);
@ -2276,7 +2276,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
if (context.hasBinding() && e.primitiveValue() != null) { if (context.hasBinding() && e.primitiveValue() != null) {
checkPrimitiveBinding(errors, path, type, context, e, profile, node); checkPrimitiveBinding(hostContext, errors, path, type, context, e, profile, node);
} }
if (type.equals("xhtml")) { if (type.equals("xhtml")) {
@ -2540,7 +2540,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) { private void checkPrimitiveBinding(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) {
// We ignore bindings that aren't on string, uri or code // We ignore bindings that aren't on string, uri or code
if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) { if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) {
return; return;
@ -2561,29 +2561,37 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet())); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
} }
} else { } else {
long t = System.nanoTime(); CodedContentValidationPolicy validationPolicy = getPolicyAdvisor() == null ?
ValidationResult vr = null; CodedContentValidationPolicy.VALUESET : getPolicyAdvisor().policyForCodedContent(this, hostContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, vs, new ArrayList<>());
if (binding.getStrength() != BindingStrength.EXAMPLE) {
ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem(); if (validationPolicy != CodedContentValidationPolicy.IGNORE) {
vr = checkCodeOnServer(stack, vs, value, options); long t = System.nanoTime();
} ValidationResult vr = null;
timeTracker.tx(t, "vc "+value+""); if (binding.getStrength() != BindingStrength.EXAMPLE) {
if (binding.getStrength() == BindingStrength.REQUIRED) { ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem();
removeTrackedMessagesForLocation(errors, element, path); if (validationPolicy == CodedContentValidationPolicy.CODE) {
} options = options.noCheckValueSetMembership();
if (vr != null && !vr.isOk()) { }
if (vr.IsNoService()) vr = checkCodeOnServer(stack, vs, value, options);
txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value); }
else if (binding.getStrength() == BindingStrength.REQUIRED) timeTracker.tx(t, "vc "+value+"");
txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); if (binding.getStrength() == BindingStrength.REQUIRED) {
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { removeTrackedMessagesForLocation(errors, element, path);
if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) }
checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack); if (vr != null && !vr.isOk()) {
else if (!noExtensibleWarnings && !isOkExtension(value, vs)) if (vr.IsNoService())
txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value);
} else if (binding.getStrength() == BindingStrength.PREFERRED) { else if (binding.getStrength() == BindingStrength.REQUIRED)
if (baseOnly) { txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage()));
txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage())); else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack);
else if (!noExtensibleWarnings && !isOkExtension(value, vs))
txWarningForLaterRemoval(element, errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage()));
} else if (binding.getStrength() == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage()));
}
} }
} }
} }
@ -2724,7 +2732,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref); warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref);
ResolvedReference we = localResolve(ref, stack, errors, path, (Element) hostContext.getAppContext(), element); ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), element);
String refType; String refType;
if (ref.startsWith("#")) { if (ref.startsWith("#")) {
refType = "contained"; refType = "contained";
@ -3412,7 +3420,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return true; return true;
} }
private ResolvedReference localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path, Element hostContext, Element source) { private ResolvedReference localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path, Element rootResource, Element source) {
if (ref.startsWith("#")) { if (ref.startsWith("#")) {
// work back through the parent list. // work back through the parent list.
// really, there should only be one level for this (contained resources cannot contain // really, there should only be one level for this (contained resources cannot contain
@ -3512,11 +3520,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
stack = stack.getParent(); stack = stack.getParent();
} }
// we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity.
if (hostContext != null && BUNDLE.equals(hostContext.fhirType())) { if (rootResource != null && BUNDLE.equals(rootResource.fhirType())) {
String type = hostContext.getChildValue(TYPE); String type = rootResource.getChildValue(TYPE);
Element entry = getEntryForSource(hostContext, source); Element entry = getEntryForSource(rootResource, source);
fullUrl = entry.getChildValue(FULL_URL); fullUrl = entry.getChildValue(FULL_URL);
IndexedElement res = getFromBundle(hostContext, ref, fullUrl, errors, path, type, "transaction".equals(type)); IndexedElement res = getFromBundle(rootResource, ref, fullUrl, errors, path, type, "transaction".equals(type));
if (res == null) { if (res == null) {
return null; return null;
} else { } else {
@ -3524,7 +3532,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rr.setResource(res.getMatch()); rr.setResource(res.getMatch());
rr.setFocus(res.getMatch()); rr.setFocus(res.getMatch());
rr.setExternal(false); rr.setExternal(false);
rr.setStack(new NodeStack(context, null, hostContext, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(), rr.setStack(new NodeStack(context, null, rootResource, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1, res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition())); res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")"); rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")");

View File

@ -114,7 +114,8 @@ public class ComparisonTests {
BaseWorkerContext bc = (BaseWorkerContext) context; BaseWorkerContext bc = (BaseWorkerContext) context;
boolean dupl = bc.isAllowLoadingDuplicates(); boolean dupl = bc.isAllowLoadingDuplicates();
bc.setAllowLoadingDuplicates(true); bc.setAllowLoadingDuplicates(true);
context.loadFromPackage(npm, new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5())); context.loadFromPackage(npm, new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"},
new NullLoaderKnowledgeProviderR5(), context.getVersion()));
bc.setAllowLoadingDuplicates(dupl); bc.setAllowLoadingDuplicates(dupl);
} }

View File

@ -107,7 +107,7 @@ public class UtilitiesXTests {
if (version.startsWith("3.0")) if (version.startsWith("3.0"))
return new R3ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5()); return new R3ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5());
if (version.startsWith("4.0")) if (version.startsWith("4.0"))
return new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5()); return new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProviderR5(), version);
return null; return null;
} }

View File

@ -33,6 +33,7 @@ import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.FhirPublication; import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Patient;
@ -46,6 +47,8 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule; import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
@ -527,6 +530,14 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
return ContainedReferenceValidationPolicy.CHECK_VALID; return ContainedReferenceValidationPolicy.CHECK_VALID;
} }
@Override
public CodedContentValidationPolicy policyForCodedContent(IResourceValidator validator, Object appContext, String stackPath, ElementDefinition definition,
StructureDefinition structure, BindingKind kind, ValueSet valueSet, List<String> systems) {
if (content.has("validateCodedContent"))
return CodedContentValidationPolicy.valueOf(content.get("validateCodedContent").getAsString());
else
return CodedContentValidationPolicy.VALUESET;
}
@Override @Override
public boolean resolveURL(IResourceValidator validator, Object appContext, String path, String url, String type) throws IOException, FHIRException { public boolean resolveURL(IResourceValidator validator, Object appContext, String path, String url, String type) throws IOException, FHIRException {
return !url.contains("example.org") && !url.startsWith("http://hl7.org/fhir/invalid"); return !url.contains("example.org") && !url.startsWith("http://hl7.org/fhir/invalid");
@ -581,4 +592,5 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
public boolean fetchesCanonicalResource(IResourceValidator validator, String url) { public boolean fetchesCanonicalResource(IResourceValidator validator, String url) {
return false; return false;
} }
} }