001package org.hl7.fhir.validation.instance.type; 002 003import java.util.List; 004 005import org.hl7.fhir.exceptions.FHIRException; 006import org.hl7.fhir.r5.context.IWorkerContext; 007import org.hl7.fhir.r5.elementmodel.Element; 008import org.hl7.fhir.r5.model.CodeSystem; 009import org.hl7.fhir.r5.model.ValueSet; 010import org.hl7.fhir.r5.utils.XVerExtensionManager; 011import org.hl7.fhir.utilities.Utilities; 012import org.hl7.fhir.utilities.i18n.I18nConstants; 013import org.hl7.fhir.utilities.validation.ValidationMessage; 014import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 015import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 016import org.hl7.fhir.utilities.validation.ValidationOptions; 017import org.hl7.fhir.validation.BaseValidator; 018import org.hl7.fhir.validation.TimeTracker; 019import org.hl7.fhir.validation.instance.utils.NodeStack; 020 021import ca.uhn.fhir.validation.ValidationResult; 022 023public class CodeSystemValidator extends BaseValidator { 024 025 public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager) { 026 super(context, xverManager); 027 source = Source.InstanceValidator; 028 this.timeTracker = timeTracker; 029 } 030 031 public void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack, ValidationOptions options) { 032 String url = cs.getNamedChildValue("url"); 033 String content = cs.getNamedChildValue("content"); 034 String caseSensitive = cs.getNamedChildValue("caseSensitive"); 035 String hierarchyMeaning = cs.getNamedChildValue("hierarchyMeaning"); 036 String supp = cs.getNamedChildValue("supplements"); 037 038 metaChecks(errors, cs, stack, url, content, caseSensitive, hierarchyMeaning, !Utilities.noString(supp)); 039 040 String vsu = cs.getNamedChildValue("valueSet"); 041 if (!Utilities.noString(vsu)) { 042 hint(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE); 043 ValueSet vs; 044 try { 045 vs = context.fetchResourceWithException(ValueSet.class, vsu); 046 } catch (FHIRException e) { 047 vs = null; 048 } 049 if (vs != null) { 050 if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.hasCompose(), I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) { 051 if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().size() == 1, I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) { 052 if (rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().get(0).getSystem().equals(url), I18nConstants.CODESYSTEM_CS_VS_WRONGSYSTEM, url, vsu, vs.getCompose().getInclude().get(0).getSystem())) { 053 rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet() 054 && !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu); 055 if (vs.hasExpansion()) { 056 int count = countConcepts(cs); 057 rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getExpansion().getContains().size() == count, I18nConstants.CODESYSTEM_CS_VS_EXP_MISMATCH, url, vsu, count, vs.getExpansion().getContains().size()); 058 } 059 } 060 } 061 } 062 } 063 } // todo... try getting the value set the other way... 064 065 if (supp != null) { 066 if (context.supportsSystem(supp)) { 067 List<Element> concepts = cs.getChildrenByName("concept"); 068 int ce = 0; 069 for (Element concept : concepts) { 070 validateSupplementConcept(errors, concept, stack.push(concept, ce, null, null), supp, options); 071 ce++; 072 } 073 } else { 074 if (cs.hasChildren("concept")) { 075 warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp); 076 } 077 } 078 } 079 } 080 081 private void metaChecks(List<ValidationMessage> errors, Element cs, NodeStack stack, String url, String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement) { 082 if (isSupplement) { 083 if (!"supplement".equals(content)) { 084 NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null); 085 rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG); 086 } 087 if (!Utilities.noString(caseSensitive)) { 088 NodeStack s = stack.push(cs.getNamedChild("caseSensitive"), -1, null, null); 089 rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "caseSensitive"); 090 } 091 if (!Utilities.noString(hierarchyMeaning)) { 092 NodeStack s = stack.push(cs.getNamedChild("hierarchyMeaning"), -1, null, null); 093 rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "caseSensitive"); 094 } 095 096 } else { 097 boolean isHL7 = url != null && (url.contains("hl7.org") || url.contains("fhir.org")); 098 if (Utilities.noString(content)) { 099 NodeStack s = stack; 100 Element c = cs.getNamedChild("content"); 101 if (c != null) { 102 s = stack.push(c, -1, null, null); 103 } 104 if (isHL7) { 105 rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHALL, "content"); 106 } else { 107 warning(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "content"); 108 } 109 } else if ("supplement".equals(content)) { 110 NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null); 111 rule(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING); 112 } 113 if (Utilities.noString(caseSensitive)) { 114 NodeStack s = stack; 115 Element c = cs.getNamedChild("caseSensitive"); 116 if (c != null) { 117 s = stack.push(c, -1, null, null); 118 } 119 if (isHL7) { 120 warning(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "caseSensitive"); 121 } else { 122 hint(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "caseSensitive"); 123 } 124 } 125 if (Utilities.noString(hierarchyMeaning) && hasHeirarchy(cs)) { 126 NodeStack s = stack; 127 Element c = cs.getNamedChild("hierarchyMeaning"); 128 if (c != null) { 129 s = stack.push(c, -1, null, null); 130 } 131 if (isHL7) { 132 warning(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "hierarchyMeaning"); 133 } else { 134 hint(errors, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "hierarchyMeaning"); 135 } 136 } 137 } 138 } 139 140 141 private boolean hasHeirarchy(Element cs) { 142 for (Element c : cs.getChildren("concept")) { 143 if (c.hasChildren("concept")) { 144 return true; 145 } 146 } 147 return false; 148 } 149 150 private void validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) { 151 String code = concept.getChildValue("code"); 152 if (!Utilities.noString(code)) { 153 org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null); 154 rule(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code); 155 } 156 157 } 158 159 private int countConcepts(Element cs) { 160 List<Element> concepts = cs.getChildrenByName("concept"); 161 int res = concepts.size(); 162 for (Element concept : concepts) { 163 res = res + countConcepts(concept); 164 } 165 return res; 166 } 167 168 169}