From a6ba3e8a21e2897e3366ebd2436f0efc02cb1f8e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 15 Dec 2021 09:34:54 +1100 Subject: [PATCH 1/6] handle vs.url properly so that we can rely on just url + version for internal caching (no hashing needed) --- .../hl7/fhir/r5/renderers/QuestionnaireRenderer.java | 2 +- .../fhir/r5/terminologies/ValueSetCheckerSimple.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java index df2000946..b8afffc5c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java @@ -796,7 +796,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer { vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); if (vs != null && !vs.hasUrl()) { vs = vs.copy(); - vs.setUrl("urn:uuid:"+UUID.randomUUID().toString().toLowerCase()); + vs.setUrl(q.getUrl()+"--"+q.getContained(i.getAnswerValueSet().substring(1))); } } else { vs = context.getContext().fetchResource(ValueSet.class, i.getAnswerValueSet()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java index 706e70e7a..8285a81ba 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java @@ -613,8 +613,10 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (valueset.hasExpansion()) { return checkExpansion(new Coding(system, code, null)); } else if (valueset.hasCompose()) { + int i = 0; for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { - Boolean ok = inComponent(vsi, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + Boolean ok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + i++; if (ok == null && result == false) { result = null; } else if (ok) { @@ -623,7 +625,8 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } } for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) { - Boolean nok = inComponent(vsi, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + Boolean nok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + i++; if (nok == null && result == false) { result = null; } else if (nok != null && nok) { @@ -635,7 +638,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe return result; } - private Boolean inComponent(ConceptSetComponent vsi, String system, String code, boolean only, List warnings) throws FHIRException { + private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, List warnings) throws FHIRException { for (UriType uri : vsi.getValueSet()) { if (inImport(uri.getValue(), system, code)) { return true; @@ -662,7 +665,8 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe // make up a transient value set with ValueSet vs = new ValueSet(); vs.setStatus(PublicationStatus.ACTIVE); - vs.setUrl(Utilities.makeUuidUrn()); + vs.setUrl(valueset.getUrl()+"--"+vsiIndex); + vs.setVersion(valueset.getVersion()); vs.getCompose().addInclude(vsi); ValidationResult res = context.validateCode(options.noClient(), new Coding(system, code, null), vs); if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { From 3118cdc132d3caa0da3effa79a5e5ad209c23193 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 17 Dec 2021 12:08:40 +1100 Subject: [PATCH 2/6] Add support for new FHIR Versions --- .../org/hl7/fhir/r4/model/Enumerations.java | 12 ++-- .../org/hl7/fhir/r5/model/Enumerations.java | 66 ++++++++++++++++--- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java index 25bf7f2a5..4de16b62f 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java @@ -10089,7 +10089,7 @@ The primary difference between a medication statement and a medication administr return _4_0_1; if ("4.1.0".equals(codeString)) return _4_1_0; - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-cibuild".equals(codeString)) return _4_3_0_CIBUILD; throw new FHIRException("Unknown FHIRVersion code '"+codeString+"'"); } @@ -10123,7 +10123,7 @@ The primary difference between a medication statement and a medication administr case _4_0_0: return "4.0.0"; case _4_0_1: return "4.0.1"; case _4_1_0: return "4.1.0"; - case _4_3_0_CIBUILD: return "4.3.0-CIBUILD"; + case _4_3_0_CIBUILD: return "4.3.0-cibuild"; case NULL: return null; default: return "?"; @@ -10217,7 +10217,7 @@ The primary difference between a medication statement and a medication administr case _4_0_0: return "4.0.0"; case _4_0_1: return "4.0.1"; case _4_1_0: return "4.1.0"; - case _4_3_0_CIBUILD: return "4.3.0-CIBUILD"; + case _4_3_0_CIBUILD: return "4.3.0-cibuild"; case NULL: return null; default: return "?"; } @@ -10283,7 +10283,7 @@ The primary difference between a medication statement and a medication administr return FHIRVersion._4_0_1; if ("4.1.0".equals(codeString)) return FHIRVersion._4_1_0; - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-cibuild".equals(codeString)) return FHIRVersion._4_3_0_CIBUILD; throw new IllegalArgumentException("Unknown FHIRVersion code '"+codeString+"'"); } @@ -10343,7 +10343,7 @@ The primary difference between a medication statement and a medication administr return new Enumeration(this, FHIRVersion._4_0_1); if ("4.1.0".equals(codeString)) return new Enumeration(this, FHIRVersion._4_1_0); - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-cibuild".equals(codeString)) return new Enumeration(this, FHIRVersion._4_3_0_CIBUILD); throw new FHIRException("Unknown FHIRVersion code '"+codeString+"'"); } @@ -10397,7 +10397,7 @@ The primary difference between a medication statement and a medication administr if (code == FHIRVersion._4_1_0) return "4.1.0"; if (code == FHIRVersion._4_3_0_CIBUILD) - return "4.3.0_CIBUILD"; + return "4.3.0-cibuild"; return "?"; } public String toSystem(FHIRVersion code) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Enumerations.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Enumerations.java index 3e8b64c62..8f35ab5aa 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Enumerations.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Enumerations.java @@ -6716,6 +6716,10 @@ The MedicationUsage resource was previously called MedicationStatement. * R5 Preview #1. */ _4_2_0, + /** + * R4B + */ + _4_3_0_SNAPSHOT1, /** * R4B */ @@ -6732,6 +6736,14 @@ The MedicationUsage resource was previously called MedicationStatement. * R5 Draft Ballot. */ _4_6_0, + /** + * R5 CI Build. + */ + _5_0_0_SNAPSHOT1, + /** + * R5 CI Build. + */ + _5_0_0_CIBUILD, /** * added to help the parsers */ @@ -6790,7 +6802,9 @@ The MedicationUsage resource was previously called MedicationStatement. return _4_1_0; if ("4.2.0".equals(codeString)) return _4_2_0; - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-snapshot1".equals(codeString)) + return _4_3_0_SNAPSHOT1; + if ("4.3.0-cibuild".equals(codeString)) return _4_3_0_CIBUILD; if ("4.4.0".equals(codeString)) return _4_4_0; @@ -6798,6 +6812,10 @@ The MedicationUsage resource was previously called MedicationStatement. return _4_5_0; if ("4.6.0".equals(codeString)) return _4_6_0; + if ("5.0.0-snapshot1".equals(codeString)) + return _5_0_0_SNAPSHOT1; + if ("5.0.0-cibuild".equals(codeString)) + return _5_0_0_CIBUILD; throw new FHIRException("Unknown FHIRVersion code '"+codeString+"'"); } public String toCode() { @@ -6827,10 +6845,13 @@ The MedicationUsage resource was previously called MedicationStatement. case _4_0_1: return "4.0.1"; case _4_1_0: return "4.1.0"; case _4_2_0: return "4.2.0"; - case _4_3_0_CIBUILD: return "4.3.0-CIBUILD"; + case _4_3_0_SNAPSHOT1: return "4.3.0-snapshot1"; + case _4_3_0_CIBUILD: return "4.3.0-cibuild"; case _4_4_0: return "4.4.0"; case _4_5_0: return "4.5.0"; case _4_6_0: return "4.6.0"; + case _5_0_0_SNAPSHOT1: return "5.0.0-snapshot1"; + case _5_0_0_CIBUILD: return "5.0.0-cibuild"; case NULL: return null; default: return "?"; } @@ -6862,10 +6883,13 @@ The MedicationUsage resource was previously called MedicationStatement. case _4_0_1: return "http://hl7.org/fhir/FHIR-version"; case _4_1_0: return "http://hl7.org/fhir/FHIR-version"; case _4_2_0: return "http://hl7.org/fhir/FHIR-version"; + case _4_3_0_SNAPSHOT1: return "http://hl7.org/fhir/FHIR-version"; case _4_3_0_CIBUILD: return "http://hl7.org/fhir/FHIR-version"; case _4_4_0: return "http://hl7.org/fhir/FHIR-version"; case _4_5_0: return "http://hl7.org/fhir/FHIR-version"; case _4_6_0: return "http://hl7.org/fhir/FHIR-version"; + case _5_0_0_SNAPSHOT1: return "http://hl7.org/fhir/FHIR-version"; + case _5_0_0_CIBUILD: return "http://hl7.org/fhir/FHIR-version"; case NULL: return null; default: return "?"; } @@ -6897,10 +6921,13 @@ The MedicationUsage resource was previously called MedicationStatement. case _4_0_1: return "FHIR Release 4 (Normative + STU) with 1 technical errata."; case _4_1_0: return "Interim Version."; case _4_2_0: return "R5 Preview #1."; - case _4_3_0_CIBUILD: return "R4B CIBuild"; + case _4_3_0_SNAPSHOT1: return "R4B Snapshot #1"; + case _4_3_0_CIBUILD: return "R4B Rolling CI-Build"; case _4_4_0: return "R5 Preview #2."; case _4_5_0: return "R5 Preview #3."; - case _4_6_0: return "R5 Draft Ballot."; + case _4_6_0: return "R5 Draft Ballot"; + case _5_0_0_SNAPSHOT1: return "R5 Snapshot #1"; + case _5_0_0_CIBUILD: return "R5 Rooling CI-Build"; case NULL: return null; default: return "?"; } @@ -6932,10 +6959,13 @@ The MedicationUsage resource was previously called MedicationStatement. case _4_0_1: return "4.0.1"; case _4_1_0: return "4.1.0"; case _4_2_0: return "4.2.0"; - case _4_3_0_CIBUILD: return "4.3.0-CIBUILD"; + case _4_3_0_SNAPSHOT1: return "4.3.0-snapshot1"; + case _4_3_0_CIBUILD: return "4.3.0-cibuild"; case _4_4_0: return "4.4.0"; case _4_5_0: return "4.5.0"; case _4_6_0: return "4.6.0"; + case _5_0_0_SNAPSHOT1: return "5.0.0-snapshot1"; + case _5_0_0_CIBUILD: return "5.0.0-cibuild"; case NULL: return null; default: return "?"; } @@ -6996,7 +7026,7 @@ public String toCode(int len) { return true; if ("4.2.0".equals(codeString)) return true; - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-cibuild".equals(codeString)) return true; return false; } @@ -7069,7 +7099,9 @@ public String toCode(int len) { return FHIRVersion._4_1_0; if ("4.2.0".equals(codeString)) return FHIRVersion._4_2_0; - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-snapshot1".equals(codeString)) + return FHIRVersion._4_3_0_SNAPSHOT1; + if ("4.3.0-cibuild".equals(codeString)) return FHIRVersion._4_3_0_CIBUILD; if ("4.4.0".equals(codeString)) return FHIRVersion._4_4_0; @@ -7077,6 +7109,10 @@ public String toCode(int len) { return FHIRVersion._4_5_0; if ("4.6.0".equals(codeString)) return FHIRVersion._4_6_0; + if ("5.0.0-snapshot1".equals(codeString)) + return FHIRVersion._5_0_0_SNAPSHOT1; + if ("5.0.0-cibuild".equals(codeString)) + return FHIRVersion._5_0_0_CIBUILD; throw new IllegalArgumentException("Unknown FHIRVersion code '"+codeString+"'"); } public Enumeration fromType(Base code) throws FHIRException { @@ -7137,7 +7173,9 @@ public String toCode(int len) { return new Enumeration(this, FHIRVersion._4_1_0); if ("4.2.0".equals(codeString)) return new Enumeration(this, FHIRVersion._4_2_0); - if ("4.3.0-CIBUILD".equals(codeString)) + if ("4.3.0-snapshot1".equals(codeString)) + return new Enumeration(this, FHIRVersion._4_3_0_SNAPSHOT1); + if ("4.3.0-cibuild".equals(codeString)) return new Enumeration(this, FHIRVersion._4_3_0_CIBUILD); if ("4.4.0".equals(codeString)) return new Enumeration(this, FHIRVersion._4_4_0); @@ -7145,6 +7183,10 @@ public String toCode(int len) { return new Enumeration(this, FHIRVersion._4_5_0); if ("4.6.0".equals(codeString)) return new Enumeration(this, FHIRVersion._4_6_0); + if ("5.0.0-snapshot1".equals(codeString)) + return new Enumeration(this, FHIRVersion._5_0_0_SNAPSHOT1); + if ("5.0.0-cibuild".equals(codeString)) + return new Enumeration(this, FHIRVersion._5_0_0_CIBUILD); throw new FHIRException("Unknown FHIRVersion code '"+codeString+"'"); } public String toCode(FHIRVersion code) { @@ -7198,14 +7240,20 @@ public String toCode(int len) { return "4.1.0"; if (code == FHIRVersion._4_2_0) return "4.2.0"; + if (code == FHIRVersion._4_3_0_SNAPSHOT1) + return "4.3.0-snapshot1"; if (code == FHIRVersion._4_3_0_CIBUILD) - return "4.3.0-CIBUILD"; + return "4.3.0-cibuild"; if (code == FHIRVersion._4_4_0) return "4.4.0"; if (code == FHIRVersion._4_5_0) return "4.5.0"; if (code == FHIRVersion._4_6_0) return "4.6.0"; + if (code == FHIRVersion._5_0_0_SNAPSHOT1) + return "5.0.0-snapshot1"; + if (code == FHIRVersion._5_0_0_CIBUILD) + return "5.0.0-cibuild"; return "?"; } public String toSystem(FHIRVersion code) { From a13a43ffdf277a51fe3f00d0eaaab3a3b08687c2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 17 Dec 2021 12:09:29 +1100 Subject: [PATCH 3/6] check StructureDefinition derivation consistency --- RELEASE_NOTES.md | 5 ++++ .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 2 +- .../instance/InstanceValidator.java | 23 ++++++++++++------- .../type/StructureDefinitionValidator.java | 2 ++ 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e69de29bb..3743ad53e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -0,0 +1,5 @@ +Validator: +* check StructureDefinition derivation consistency + +Other code changes: +* Add support for new FHIR releases \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 1a67ff30d..5deaf2ba9 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -361,6 +361,7 @@ public class I18nConstants { public static final String SD_MUST_HAVE_DERIVATION = "SD_MUST_HAVE_DERIVATION"; public static final String SD_NESTED_MUST_SUPPORT_DIFF = "SD_NESTED_MUST_SUPPORT_DIFF"; public static final String SD_NESTED_MUST_SUPPORT_SNAPSHOT = "SD_NESTED_MUST_SUPPORT_SNAPSHOT"; + public static final String SD_DERIVATION_KIND_MISMATCH = "SD_DERIVATION_KIND_MISMATCH"; public static final String SD_ED_TYPE_PROFILE_UNKNOWN = "SD_ED_TYPE_PROFILE_UNKNOWN"; public static final String SD_ED_TYPE_PROFILE_NOTYPE = "SD_ED_TYPE_PROFILE_NOTYPE"; public static final String SD_ED_TYPE_PROFILE_WRONG = "SD_ED_TYPE_PROFILE_WRONG"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index bf567fc41..0d5935dcb 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -701,4 +701,4 @@ TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG = The value in the instance ({2}) is TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM = The value in the instance ({0} {1}) is greater than the specified maxValue ({2} {3}) after UCUM conversion TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR = Base64 encoded values are not allowed to contain any whitespace (per RFC 4648). Note that non-validating readers are encouraged to accept whitespace anyway TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING = Base64 encoded values SHOULD not contain any whitespace (per RFC 4648). Note that non-validating readers are encouraged to accept whitespace anyway - +SD_DERIVATION_KIND_MISMATCH = The structure definition constrains a kind of {0}, but has a different kind ({1}) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index a2b3820d9..416219e29 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -1312,7 +1312,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String system = c.getSystem(); String display = c.getDisplay(); String version = c.getVersion(); - rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); + rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isCodeSystemReferenceValid(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); if (system != null && code != null && !noTerminologyChecks) { rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system); @@ -1568,7 +1568,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkCodedElement(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, String theCode, String theSystem, String theVersion, String theDisplay) { - rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); + rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE); if (theSystem != null && theCode != null && !noTerminologyChecks) { @@ -2023,7 +2023,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkIdentifier(List errors, String path, Element element, ElementDefinition context) { String system = element.getNamedChildValue("system"); - rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM); + rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM); if ("urn:ietf:rfc:3986".equals(system)) { String value = element.getNamedChildValue("value"); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE); @@ -3488,13 +3488,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z")); } - private boolean isAbsolute(String uri) { - return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:") - || uri.startsWith("urn:iso:") || uri.startsWith("urn:iso-astm:") || uri.startsWith("mailto:")|| isValidFHIRUrn(uri); + private boolean isAbsolute(String uri, String... protocols) { + return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:"); } - private boolean isValidFHIRUrn(String uri) { - return (uri.equals("urn:x-fhir:uk:id:nhs-number")) || uri.startsWith("urn:"); // Anyone can invent a URN, so why should we complain? + private boolean isCodeSystemReferenceValid(String uri) { + return isSystemReferenceValid(uri); + } + + private boolean isIdentifierSystemReferenceValid(String uri) { + return isSystemReferenceValid(uri) || uri.startsWith("ldap:"); + } + + private boolean isSystemReferenceValid(String uri) { + return uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:"); } public boolean isAnyExtensionsAllowed() { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 3164ba886..de19f58f9 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -97,6 +97,8 @@ public class StructureDefinitionValidator extends BaseValidator { } } } + rule(errors, IssueType.NOTFOUND, stack.getLiteralPath(), base.getKindElement().primitiveValue().equals(src.getChildValue("kind")), + I18nConstants.SD_DERIVATION_KIND_MISMATCH, base.getKindElement().primitiveValue(), src.getChildValue("kind")); } } catch (FHIRException | IOException e) { rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); From 47dd580868ed62107d15f1d1a52ac713deed7beb Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 17 Dec 2021 12:21:38 +1100 Subject: [PATCH 4/6] Add searchparameter analyser --- .../convertors/analytics/PackageVisitor.java | 188 ++++++++++++++++ .../analytics/SearchParameterAnalysis.java | 211 ++++++++++++++++++ .../org/hl7/fhir/r5/utils/ResourceFixer.java | 111 +++++++++ 3 files changed, 510 insertions(+) create mode 100644 org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java create mode 100644 org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/SearchParameterAnalysis.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceFixer.java diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java new file mode 100644 index 000000000..1608bfbe0 --- /dev/null +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/PackageVisitor.java @@ -0,0 +1,188 @@ +package org.hl7.fhir.convertors.analytics; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; +import org.hl7.fhir.utilities.json.JSONUtil; +import org.hl7.fhir.utilities.json.JsonTrackingParser; +import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; +import org.hl7.fhir.utilities.npm.NpmPackage; +import org.hl7.fhir.utilities.npm.PackageClient; +import org.hl7.fhir.utilities.npm.PackageInfo; +import org.hl7.fhir.utilities.npm.ToolsVersion; +import org.hl7.fhir.utilities.xml.XMLUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +public class PackageVisitor { + + public interface IPackageVisitorProcessor { + public void processResource(String pid, String version, String type, byte[] content) throws FHIRException; + } + + private List resourceTypes = new ArrayList<>(); + private List versions = new ArrayList<>(); + private boolean corePackages; + private boolean oldVersions; + private IPackageVisitorProcessor processor; + private FilesystemPackageCacheManager pcm; + private PackageClient pc; + + public List getResourceTypes() { + return resourceTypes; + } + + public void setResourceTypes(List resourceTypes) { + this.resourceTypes = resourceTypes; + } + + public List getVersions() { + return versions; + } + + public void setVersions(List versions) { + this.versions = versions; + } + + + + + public boolean isCorePackages() { + return corePackages; + } + + + + + public void setCorePackages(boolean corePackages) { + this.corePackages = corePackages; + } + + + + + public boolean isOldVersions() { + return oldVersions; + } + + + + + public void setOldVersions(boolean oldVersions) { + this.oldVersions = oldVersions; + } + + + + + public IPackageVisitorProcessor getProcessor() { + return processor; + } + + public void setProcessor(IPackageVisitorProcessor processor) { + this.processor = processor; + } + + public void visitPackages() throws IOException, ParserConfigurationException, SAXException { + System.out.println("Finding packages"); + pc = new PackageClient(PackageClient.PRIMARY_SERVER); + pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + Set pidList = getAllPackages(); + System.out.println("Go: "+pidList.size()+" packages"); + for (String pid : pidList) { + List vList = listVersions(pid); + if (oldVersions) { + for (String v : vList) { + processPackage(pid, v); + } + } else if (vList.isEmpty()) { + System.out.println("No Packages for "+pid); + } else { + processPackage(pid, vList.get(vList.size() - 1)); + } + } + } + + private List listVersions(String pid) throws IOException { + List list = new ArrayList<>(); + if (pid !=null) { + for (PackageInfo i : pc.getVersions(pid)) { + list.add(i.getVersion()); + } + } + return list; + } + + private Set getAllPackages() throws IOException, ParserConfigurationException, SAXException { + Set list = new HashSet<>(); + for (PackageInfo i : pc.search(null, null, null, false)) { + list.add(i.getId()); + } + JsonObject json = JsonTrackingParser.fetchJson("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json"); + for (JsonObject ig : JSONUtil.objects(json, "guides")) { + list.add(JSONUtil.str(ig, "npm-name")); + } + json = JsonTrackingParser.fetchJson("https://raw.githubusercontent.com/FHIR/ig-registry/master/package-feeds.json"); + for (JsonObject feed : JSONUtil.objects(json, "feeds")) { + processFeed(list, JSONUtil.str(feed, "url")); + } + + return list; + } + + private void processFeed(Set list, String str) throws IOException, ParserConfigurationException, SAXException { + System.out.println("Feed "+str); + try { + SimpleHTTPClient fetcher = new SimpleHTTPClient(); + HTTPResult res = fetcher.get(str+"?nocache=" + System.currentTimeMillis()); + res.checkThrowException(); + Document xml = XMLUtil.parseToDom(res.getContent()); + for (Element channel : XMLUtil.getNamedChildren(xml.getDocumentElement(), "channel")) { + for (Element item : XMLUtil.getNamedChildren(channel, "item")) { + String pid = XMLUtil.getNamedChildText(item, "title"); + if (pid.contains("#")) { + list.add(pid.substring(0, pid.indexOf("#"))); + } + } + } + } catch (Exception e) { + System.out.println(" "+e.getMessage()); + } + } + + + private void processPackage(String pid, String v) throws IOException { + NpmPackage npm = null; + String fv = null; + try { + npm = pcm.loadPackage(pid, v); + fv = npm.fhirVersion(); + } catch (Throwable e) { + System.out.println("Unable to process: "+pid+"#"+v+": "+e.getMessage()); + } + int c = 0; + if (fv != null && (versions.isEmpty() || versions.contains(fv))) { + for (String type : resourceTypes) { + for (String s : npm.listResources(type)) { + c++; + processor.processResource(pid+"#"+v, fv, type, TextFile.streamToBytes(npm.load("package", s))); + } + } + } + System.out.println("Processed: "+pid+"#"+v+": "+c+" resources"); + } + +} diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/SearchParameterAnalysis.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/SearchParameterAnalysis.java new file mode 100644 index 000000000..2b8036620 --- /dev/null +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/analytics/SearchParameterAnalysis.java @@ -0,0 +1,211 @@ +package org.hl7.fhir.convertors.analytics; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.convertors.analytics.PackageVisitor.IPackageVisitorProcessor; +import org.hl7.fhir.dstu2.model.SearchParameter; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.xml.sax.SAXException; + +public class SearchParameterAnalysis implements IPackageVisitorProcessor { + + public static class SearchParameterTypeUsage { + private Set coreUsage = new HashSet<>(); + private Set igUsage = new HashSet<>(); + public String summary() { + return ""+coreUsage.size()+" / "+igUsage.size(); + } + } + + public static class SearchParameterType { + private Map usages = new HashMap<>(); + public void seeUsage(boolean core, String usage, String url) { + if (!usages.containsKey(usage)) { + usages.put(usage, new SearchParameterTypeUsage()); + } + SearchParameterTypeUsage tu = usages.get(usage); + if (core) { + tu.coreUsage.add(url); + } else { + tu.igUsage.add(url); + } + + } + } + + public static class SearchParameterVersionAnalysis { + private Map types = new HashMap<>(); + private String version; + + public void seeUsage(boolean core, String type, String usage, String url) { +// System.out.println("v"+version+" "+Utilities.padRight(url, ' ', 60)+" "+type+"/"+usage); + if (type == null) { + type = "n/a"; + } + if (usage == null) { + usage = "n/a"; + } + if (!types.containsKey(type)) { + types.put(type, new SearchParameterType()); + } + SearchParameterType tu = types.get(type); + tu.seeUsage(core, usage, url); + } + + public void printSummary() { + Set usages = new HashSet<>(); + for (SearchParameterType tu : types.values()) { + usages.addAll(tu.usages.keySet()); + } + List ul = new ArrayList(); + ul.addAll(usages); + Collections.sort(ul); + System.out.print(Utilities.padRight("", ' ', 10)); + for (String u : ul) { + System.out.print(Utilities.padRight(u, ' ', 10)); + } + System.out.println(); + for (String t : types.keySet()) { + System.out.print(Utilities.padRight(t, ' ', 10)); + SearchParameterType tu = types.get(t); + for (String u : ul) { + SearchParameterTypeUsage uu = tu.usages.get(u); + if (uu == null) { + System.out.print(Utilities.padRight("0 / 0", ' ', 10)); + } else { + System.out.print(Utilities.padRight(uu.summary(), ' ', 10)); + } + } + System.out.println(); + } + + } + + } + + private Map versions = new HashMap(); + + @Override + public void processResource(String pid, String version, String type, byte[] content) throws FHIRException { +// System.out.println("v"+version+" "+type+" from "+pid); + boolean core = pid.startsWith("hl7.fhir.r") && (pid.contains(".core") || pid.contains(".examples")); + version = VersionUtilities.getMajMin(version); + if (!versions.containsKey(version)) { + versions.put(version, new SearchParameterVersionAnalysis()); + versions.get(version).version = version; + } + try { + if (VersionUtilities.isR5Ver(version)) { + processR5SP(core, versions.get(version), content); + } else if (VersionUtilities.isR4BVer(version)) { + processR4SP(core, versions.get(version), content); + } else if (VersionUtilities.isR4Ver(version)) { + processR4SP(core, versions.get(version), content); + } else if (VersionUtilities.isR3Ver(version)) { + processR3SP(core, versions.get(version), content); + } else if (VersionUtilities.isR2Ver(version)) { + processR2SP(core, versions.get(version), content); + } + } catch (IOException e) { + throw new FHIRException(e); + } + } + + private void processR5SP(boolean core, SearchParameterVersionAnalysis analysis, byte[] content) throws FHIRFormatError, IOException { + org.hl7.fhir.r5.model.Resource res = new org.hl7.fhir.r5.formats.JsonParser().parse(content); + if (res instanceof org.hl7.fhir.r5.model.Bundle) { + for (org.hl7.fhir.r5.model.Bundle.BundleEntryComponent bnd : ((org.hl7.fhir.r5.model.Bundle) res).getEntry()) { + if (bnd.getResource() != null && bnd.getResource() instanceof org.hl7.fhir.r5.model.SearchParameter) { + org.hl7.fhir.r5.model.SearchParameter sp = (org.hl7.fhir.r5.model.SearchParameter) bnd.getResource(); + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + } else { + org.hl7.fhir.r5.model.SearchParameter sp = (org.hl7.fhir.r5.model.SearchParameter) res; + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + + private void processR4SP(boolean core, SearchParameterVersionAnalysis analysis, byte[] content) throws FHIRFormatError, IOException { + org.hl7.fhir.r4.model.Resource res = new org.hl7.fhir.r4.formats.JsonParser().parse(content); + if (res instanceof org.hl7.fhir.r4.model.Bundle) { + for (org.hl7.fhir.r4.model.Bundle.BundleEntryComponent bnd : ((org.hl7.fhir.r4.model.Bundle) res).getEntry()) { + if (bnd.getResource() != null && bnd.getResource() instanceof org.hl7.fhir.r4.model.SearchParameter) { + org.hl7.fhir.r4.model.SearchParameter sp = (org.hl7.fhir.r4.model.SearchParameter) bnd.getResource(); + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + } else { + org.hl7.fhir.r4.model.SearchParameter sp = (org.hl7.fhir.r4.model.SearchParameter) res; + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + + private void processR3SP(boolean core, SearchParameterVersionAnalysis analysis, byte[] content) throws FHIRFormatError, IOException { + org.hl7.fhir.dstu3.model.Resource res = new org.hl7.fhir.dstu3.formats.JsonParser().parse(content); + if (res instanceof org.hl7.fhir.dstu3.model.Bundle) { + for (org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent bnd : ((org.hl7.fhir.dstu3.model.Bundle) res).getEntry()) { + if (bnd.getResource() != null && bnd.getResource() instanceof org.hl7.fhir.dstu3.model.SearchParameter) { + org.hl7.fhir.dstu3.model.SearchParameter sp = (org.hl7.fhir.dstu3.model.SearchParameter) bnd.getResource(); + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + } else { + org.hl7.fhir.dstu3.model.SearchParameter sp = (org.hl7.fhir.dstu3.model.SearchParameter) res; + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + + private void processR2SP(boolean core, SearchParameterVersionAnalysis analysis, byte[] content) throws FHIRFormatError, IOException { + org.hl7.fhir.dstu2.model.Resource res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(content); + if (res instanceof org.hl7.fhir.dstu2.model.Bundle) { + for (org.hl7.fhir.dstu2.model.Bundle.BundleEntryComponent bnd : ((org.hl7.fhir.dstu2.model.Bundle) res).getEntry()) { + if (bnd.getResource() != null && bnd.getResource() instanceof org.hl7.fhir.dstu2.model.SearchParameter) { + org.hl7.fhir.dstu2.model.SearchParameter sp = (org.hl7.fhir.dstu2.model.SearchParameter) bnd.getResource(); + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + } else { + org.hl7.fhir.dstu2.model.SearchParameter sp = (org.hl7.fhir.dstu2.model.SearchParameter) res; + analysis.seeUsage(core, sp.getTypeElement().primitiveValue(), sp.getXpathUsageElement().primitiveValue(), sp.getUrl()); + } + } + + public static void main(String[] args) throws Exception { + new SearchParameterAnalysis().execute(); + } + + private void execute() throws IOException, ParserConfigurationException, SAXException { + PackageVisitor pv = new PackageVisitor(); + pv.getResourceTypes().add("SearchParameter"); + pv.getResourceTypes().add("Bundle"); + pv.setOldVersions(false); + pv.setCorePackages(true); + pv.setProcessor(this); + pv.visitPackages(); + + printSummary(); + } + + private void printSummary() { + for (String v : versions.keySet()) { + System.out.println("-- v"+v+"---------------------"); + versions.get(v).printSummary(); + System.out.println(""); + } + + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceFixer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceFixer.java new file mode 100644 index 000000000..d8f63649d --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ResourceFixer.java @@ -0,0 +1,111 @@ +package org.hl7.fhir.r5.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; +import org.hl7.fhir.r5.model.CodeSystem.CodeSystemHierarchyMeaning; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.Resource; + +public class ResourceFixer { + + + public static void main(String[] args) { + new ResourceFixer().vistAllResources(args[0]); + + } + + private Set refs = new HashSet<>(); + + private void vistAllResources(String folder) { + + for (File f : new File(folder).listFiles()) { + if (f.isDirectory()) { + vistAllResources(f.getAbsolutePath()); + } else if (f.getName().endsWith(".json")) { + Resource r = null; + try { + r = new JsonParser().parse(new FileInputStream(f)); + } catch (Throwable e) { + // nothing at all + } + if (r != null) { + try { + if (visitResource(r)) { + new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(f), r); + } + } catch (Exception e) { + System.out.println("Error processing "+f.getAbsolutePath()+": "+e.getMessage()); +// e.printStackTrace(); + } + } + } else if (f.getName().endsWith(".xml")) { + Resource r = null; + try { + r = new XmlParser().parse(new FileInputStream(f)); + } catch (Throwable e) { + // nothing at all + } + if (r != null) { + try { + if (visitResource(r)) { + new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(f), r); + } + } catch (Exception e) { + System.out.println("Error processing "+f.getAbsolutePath()+": "+e.getMessage()); +// e.printStackTrace(); + } + } + } + } + } + + private boolean visitResource(Resource r) { + if (r.hasId()) { + String ref = r.fhirType()+"/"+r.getId(); + if (refs.contains(ref)) { + throw new FHIRException("Duplicate resource "+ref); + } + refs.add(ref); + } + if (r instanceof CodeSystem) { + return visitCodeSystem((CodeSystem) r); + } + return false; + } + + private boolean visitCodeSystem(CodeSystem cs) { + if (!cs.hasContent()) { + System.out.println("Setting content = complete for CodeSystem/"+cs.getId()); + cs.setContent(CodeSystemContentMode.COMPLETE); + return true; + } else if (!cs.hasHierarchyMeaning() && hasHierarchy(cs)) { + System.out.println("Setting hierarchyMeaning = is-a for CodeSystem/"+cs.getId()); + cs.setHierarchyMeaning(CodeSystemHierarchyMeaning.ISA); + return true; + } else { + return false; + } + } + + private boolean hasHierarchy(CodeSystem cs) { + for (ConceptDefinitionComponent c : cs.getConcept()) { + if (c.hasConcept()) { + return true; + } + } + return false; + } + +} From e4d30471382f7e65fbf9786a9501cb57f8bae008 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 17 Dec 2021 14:38:21 +1100 Subject: [PATCH 5/6] #357: fix bug looking for contained resources inside bundles --- RELEASE_NOTES.md | 1 + .../java/org/hl7/fhir/r4/model/Enumerations.java | 13 +++++++++++++ .../fhir/validation/instance/InstanceValidator.java | 10 ++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3743ad53e..3b538c3bf 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,6 @@ Validator: * check StructureDefinition derivation consistency +* fix bug looking for contained resources inside bundles Other code changes: * Add support for new FHIR releases \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java index 4de16b62f..87c17880c 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Enumerations.java @@ -10036,6 +10036,7 @@ The primary difference between a medication statement and a medication administr * R4B - manually added */ _4_1_0, + _4_3_0_SNAPSHOT1, _4_3_0_CIBUILD, NULL; public static FHIRVersion fromCode(String codeString) throws FHIRException { @@ -10089,6 +10090,8 @@ The primary difference between a medication statement and a medication administr return _4_0_1; if ("4.1.0".equals(codeString)) return _4_1_0; + if ("4.3.0-snapshot1".equals(codeString)) + return _4_3_0_SNAPSHOT1; if ("4.3.0-cibuild".equals(codeString)) return _4_3_0_CIBUILD; throw new FHIRException("Unknown FHIRVersion code '"+codeString+"'"); @@ -10123,6 +10126,7 @@ The primary difference between a medication statement and a medication administr case _4_0_0: return "4.0.0"; case _4_0_1: return "4.0.1"; case _4_1_0: return "4.1.0"; + case _4_3_0_SNAPSHOT1: return "4.3.0-snapshot1"; case _4_3_0_CIBUILD: return "4.3.0-cibuild"; case NULL: return null; @@ -10155,6 +10159,7 @@ The primary difference between a medication statement and a medication administr case _4_0_0: return "http://hl7.org/fhir/FHIR-version"; case _4_0_1: return "http://hl7.org/fhir/FHIR-version"; case _4_1_0: return "http://hl7.org/fhir/FHIR-version"; + case _4_3_0_SNAPSHOT1: return "http://hl7.org/fhir/FHIR-version"; case _4_3_0_CIBUILD: return "http://hl7.org/fhir/FHIR-version"; case NULL: return null; default: return "?"; @@ -10186,6 +10191,7 @@ The primary difference between a medication statement and a medication administr case _4_0_0: return "FHIR Release 4 (Normative + STU)."; case _4_0_1: return "FHIR Release 4 Technical Correction #1."; case _4_1_0: return "FHIR Release 4B Ballot #1"; + case _4_3_0_SNAPSHOT1: return "FHIR Release 4B Snapshot #1"; case _4_3_0_CIBUILD: return "FHIR Release 4B CI-Builld"; case NULL: return null; default: return "?"; @@ -10217,6 +10223,7 @@ The primary difference between a medication statement and a medication administr case _4_0_0: return "4.0.0"; case _4_0_1: return "4.0.1"; case _4_1_0: return "4.1.0"; + case _4_3_0_SNAPSHOT1: return "4.3.0-snapshot"; case _4_3_0_CIBUILD: return "4.3.0-cibuild"; case NULL: return null; default: return "?"; @@ -10283,6 +10290,8 @@ The primary difference between a medication statement and a medication administr return FHIRVersion._4_0_1; if ("4.1.0".equals(codeString)) return FHIRVersion._4_1_0; + if ("4.3.0-snapshot1".equals(codeString)) + return FHIRVersion._4_3_0_SNAPSHOT1; if ("4.3.0-cibuild".equals(codeString)) return FHIRVersion._4_3_0_CIBUILD; throw new IllegalArgumentException("Unknown FHIRVersion code '"+codeString+"'"); @@ -10343,6 +10352,8 @@ The primary difference between a medication statement and a medication administr return new Enumeration(this, FHIRVersion._4_0_1); if ("4.1.0".equals(codeString)) return new Enumeration(this, FHIRVersion._4_1_0); + if ("4.3.0-snapshot1".equals(codeString)) + return new Enumeration(this, FHIRVersion._4_3_0_SNAPSHOT1); if ("4.3.0-cibuild".equals(codeString)) return new Enumeration(this, FHIRVersion._4_3_0_CIBUILD); throw new FHIRException("Unknown FHIRVersion code '"+codeString+"'"); @@ -10396,6 +10407,8 @@ The primary difference between a medication statement and a medication administr return "4.0.1"; if (code == FHIRVersion._4_1_0) return "4.1.0"; + if (code == FHIRVersion._4_3_0_SNAPSHOT1) + return "4.3.0-snapshot1"; if (code == FHIRVersion._4_3_0_CIBUILD) return "4.3.0-cibuild"; return "?"; diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 416219e29..fe24e96ef 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -304,8 +304,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } else if (item instanceof Element) { Element e = (Element) item; - if (e.getSpecial() != null) { + if (e.getSpecial() == SpecialElement.CONTAINED) { self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); + } else if (e.getSpecial() != null) { + self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } else { self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } @@ -1312,7 +1314,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String system = c.getSystem(); String display = c.getDisplay(); String version = c.getVersion(); - rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isCodeSystemReferenceValid(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); + rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isCodeSystemReferenceValid(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); if (system != null && code != null && !noTerminologyChecks) { rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system); @@ -1568,7 +1570,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkCodedElement(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, String theCode, String theSystem, String theVersion, String theDisplay) { - rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); + rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, theSystem == null || isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE); if (theSystem != null && theCode != null && !noTerminologyChecks) { @@ -2023,7 +2025,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkIdentifier(List errors, String path, Element element, ElementDefinition context) { String system = element.getNamedChildValue("system"); - rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM); + rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM); if ("urn:ietf:rfc:3986".equals(system)) { String value = element.getNamedChildValue("value"); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE); From 3d2400bb5526588c939f1e254a16ca13d541bf89 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 17 Dec 2021 16:20:09 +1100 Subject: [PATCH 6/6] fix bundle link resolution (remove overloaded use of 'root resource') --- .../instance/InstanceValidator.java | 21 ++++++++-------- .../instance/utils/ValidatorHostContext.java | 25 ++++++++++++++++--- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index fe24e96ef..07f588919 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -305,9 +305,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (item instanceof Element) { Element e = (Element) item; if (e.getSpecial() == SpecialElement.CONTAINED) { - 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(), ctxt.getGroupingResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } else if (e.getSpecial() != null) { - 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, ctxt.getRootResource(), ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } else { self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage)); } @@ -2891,7 +2891,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref); - ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), element); + ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element); String refType; if (ref.startsWith("#")) { refType = "contained"; @@ -3586,7 +3586,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return true; } - private ResolvedReference localResolve(String ref, NodeStack stack, List errors, String path, Element rootResource, Element source) { + private ResolvedReference localResolve(String ref, NodeStack stack, List errors, String path, Element rootResource, Element groupingResource, Element source) { if (ref.startsWith("#")) { // work back through the parent list. // really, there should only be one level for this (contained resources cannot contain @@ -3686,11 +3686,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat stack = stack.getParent(); } // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. - if (rootResource != null && BUNDLE.equals(rootResource.fhirType())) { - String type = rootResource.getChildValue(TYPE); - Element entry = getEntryForSource(rootResource, source); + if (groupingResource != null && BUNDLE.equals(groupingResource.fhirType())) { // it could also be a Parameters resource - that case isn't handled yet + String type = groupingResource.getChildValue(TYPE); + Element entry = getEntryForSource(groupingResource, source); fullUrl = entry.getChildValue(FULL_URL); - IndexedElement res = getFromBundle(rootResource, ref, fullUrl, errors, path, type, "transaction".equals(type)); + IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type)); if (res == null) { return null; } else { @@ -3767,7 +3767,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private Element resolve(Object appContext, String ref, NodeStack stack, List errors, String path) throws IOException, FHIRException { - Element local = localResolve(ref, stack, errors, path, null, null).getFocus(); + Element local = localResolve(ref, stack, errors, path, null, null, null).getFocus(); if (local != null) return local; if (fetcher == null) @@ -4600,7 +4600,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ValidatorHostContext hc = null; if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) { resource = element; - hc = hostContext.forEntry(element); + assert Utilities.existsInList(hostContext.getRootResource().fhirType(), "Bundle", "Parameters"); + hc = hostContext.forEntry(element, hostContext.getRootResource()); // root becomes the grouping resource (should be either bundle or parameters) } else { hc = hostContext.forContained(element); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/ValidatorHostContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/ValidatorHostContext.java index af29a2188..83a180049 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/ValidatorHostContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/ValidatorHostContext.java @@ -16,6 +16,7 @@ public class ValidatorHostContext { private Element resource; // the resource that is the scope of id resolution - either the same as resource, or the resource the contains that resource. This can only be one level deep. private Element rootResource; + private Element groupingResource; // either a bundle or a parameters that holds the rootResource (for reference resolution) private StructureDefinition profile; // the profile that contains the content being validated private boolean checkSpecials = true; @@ -29,7 +30,7 @@ public class ValidatorHostContext { this.appContext = appContext; this.resource = element; this.rootResource = element; - // no container + // no groupingResource (Bundle or Parameters) dump("creating"); } @@ -37,7 +38,15 @@ public class ValidatorHostContext { this.appContext = appContext; this.resource = element; this.rootResource = root; - // no container + // no groupingResource (Bundle or Parameters) + dump("creating"); + } + + public ValidatorHostContext(Object appContext, Element element, Element root, Element groupingResource) { + this.appContext = appContext; + this.resource = element; + this.rootResource = root; + this.groupingResource = groupingResource; dump("creating"); } @@ -65,6 +74,10 @@ public class ValidatorHostContext { return this; } + public Element getGroupingResource() { + return groupingResource; + } + public StructureDefinition getProfile() { return profile; } @@ -106,15 +119,17 @@ public class ValidatorHostContext { res.rootResource = resource; res.resource = element; res.profile = profile; + res.groupingResource = groupingResource; res.dump("forContained"); return res; } - public ValidatorHostContext forEntry(Element element) { + public ValidatorHostContext forEntry(Element element, Element groupingResource) { ValidatorHostContext res = new ValidatorHostContext(appContext); res.rootResource = element; res.resource = element; res.profile = profile; + res.groupingResource = groupingResource; res.dump("forEntry"); return res; } @@ -124,6 +139,7 @@ public class ValidatorHostContext { res.resource = resource; res.rootResource = rootResource; res.profile = profile; + res.groupingResource = groupingResource; res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap>(); res.dump("forProfile "+profile.getUrl()); return res; @@ -134,6 +150,7 @@ public class ValidatorHostContext { res.resource = resource; res.rootResource = resource; res.profile = profile; + res.groupingResource = groupingResource; res.checkSpecials = false; res.dump("forLocalReference "+profile.getUrl()); return res; @@ -151,6 +168,7 @@ public class ValidatorHostContext { res.resource = resource; res.rootResource = resource; res.profile = profile; + res.groupingResource = null; res.checkSpecials = false; res.dump("forRemoteReference "+profile.getUrl()); return res; @@ -160,6 +178,7 @@ public class ValidatorHostContext { ValidatorHostContext res = new ValidatorHostContext(appContext); res.resource = resource; res.rootResource = resource; + res.groupingResource = groupingResource; res.profile = profile; res.checkSpecials = false; res.sliceRecords = new HashMap>();