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.Coding; 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.InstanceValidator; 020import org.hl7.fhir.validation.instance.utils.NodeStack; 021 022public class CodeSystemValidator extends BaseValidator { 023 024 private InstanceValidator parent; 025 026 public CodeSystemValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager, Coding jurisdiction) { 027 super(context, xverManager); 028 source = Source.InstanceValidator; 029 this.timeTracker = timeTracker; 030 this.jurisdiction = jurisdiction; 031 this.parent = parent; 032 033 } 034 035 public boolean validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack, ValidationOptions options) { 036 boolean ok = true; 037 String url = cs.getNamedChildValue("url"); 038 String content = cs.getNamedChildValue("content"); 039 String caseSensitive = cs.getNamedChildValue("caseSensitive"); 040 String hierarchyMeaning = cs.getNamedChildValue("hierarchyMeaning"); 041 String supp = cs.getNamedChildValue("supplements"); 042 043 metaChecks(errors, cs, stack, url, content, caseSensitive, hierarchyMeaning, !Utilities.noString(supp)); 044 045 String vsu = cs.getNamedChildValue("valueSet"); 046 if (!Utilities.noString(vsu)) { 047 hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), "complete".equals(content), I18nConstants.CODESYSTEM_CS_NO_VS_NOTCOMPLETE); 048 ValueSet vs; 049 try { 050 vs = context.fetchResourceWithException(ValueSet.class, vsu); 051 } catch (FHIRException e) { 052 vs = null; 053 } 054 if (vs != null) { 055 if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.hasCompose(), I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) { 056 if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getCompose().getInclude().size() == 1, I18nConstants.CODESYSTEM_CS_VS_INVALID, url, vsu)) { 057 if (rule(errors, NO_RULE_DATE, 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())) { 058 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet() 059 && !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu) && ok; 060 if (vs.hasExpansion()) { 061 int count = countConcepts(cs); 062 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getExpansion().getContains().size() == count, I18nConstants.CODESYSTEM_CS_VS_EXP_MISMATCH, url, vsu, count, vs.getExpansion().getContains().size()) && ok; 063 } 064 } else { 065 ok = false; 066 } 067 } else { 068 ok = false; 069 } 070 } else { 071 ok = false; 072 } 073 } 074 } // todo... try getting the value set the other way... 075 076 if (supp != null) { 077 if (context.supportsSystem(supp)) { 078 List<Element> concepts = cs.getChildrenByName("concept"); 079 int ce = 0; 080 for (Element concept : concepts) { 081 ok = validateSupplementConcept(errors, concept, stack.push(concept, ce, null, null), supp, options) && ok; 082 ce++; 083 } 084 } else { 085 if (cs.hasChildren("concept")) { 086 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_SUPP_CANT_CHECK, supp); 087 } 088 } 089 } 090 091 if (!stack.isContained()) { 092 ok = checkShareableCodeSystem(errors, cs, stack) && ok; 093 } 094 return ok; 095 } 096 097 098 private boolean checkShareableCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) { 099 if (parent.isForPublication()) { 100 if (isHL7(cs)) { 101 boolean ok = true; 102 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "url") && ok; 103 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "version") && ok; 104 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "title") && ok; 105 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7, "name"); 106 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "status") && ok; 107 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "experimental") && ok; 108 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "description") && ok; 109 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "content") && ok; 110 if (!"supplement".equals(cs.getChildValue("content"))) { 111 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING_HL7, "caseSensitive") && ok; 112 } 113 return ok; 114 } else { 115 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "url"); 116 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "version"); 117 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "title"); 118 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CODESYSTEM_SHAREABLE_EXTRA_MISSING, "name"); 119 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "status"); 120 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "experimental"); 121 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "description"); 122 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "content"); 123 if (!"supplement".equals(cs.getChildValue("content"))) { 124 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.CODESYSTEM_SHAREABLE_MISSING, "caseSensitive"); 125 } 126 } 127 } 128 return true; 129 } 130 131 private void metaChecks(List<ValidationMessage> errors, Element cs, NodeStack stack, String url, String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement) { 132 if (isSupplement) { 133 if (!"supplement".equals(content)) { 134 NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null); 135 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG); 136 } 137 if (!Utilities.noString(caseSensitive)) { 138 NodeStack s = stack.push(cs.getNamedChild("caseSensitive"), -1, null, null); 139 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "caseSensitive"); 140 } 141 if (!Utilities.noString(hierarchyMeaning)) { 142 NodeStack s = stack.push(cs.getNamedChild("hierarchyMeaning"), -1, null, null); 143 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL, "hierarchyMeaning"); 144 } 145 146 } else { 147 boolean isHL7 = url != null && (url.contains("hl7.org") || url.contains("fhir.org")); 148 if (Utilities.noString(content)) { 149 NodeStack s = stack; 150 Element c = cs.getNamedChild("content"); 151 if (c != null) { 152 s = stack.push(c, -1, null, null); 153 } 154 if (isHL7) { 155 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHALL, "content"); 156 } else { 157 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "content"); 158 } 159 } else if ("supplement".equals(content)) { 160 NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null); 161 rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING); 162 } 163 if (Utilities.noString(caseSensitive)) { 164 NodeStack s = stack; 165 Element c = cs.getNamedChild("caseSensitive"); 166 if (c != null) { 167 s = stack.push(c, -1, null, null); 168 } 169 if (isHL7) { 170 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "caseSensitive"); 171 } else { 172 hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "caseSensitive"); 173 } 174 } 175 if (Utilities.noString(hierarchyMeaning) && hasHeirarchy(cs)) { 176 NodeStack s = stack; 177 Element c = cs.getNamedChild("hierarchyMeaning"); 178 if (c != null) { 179 s = stack.push(c, -1, null, null); 180 } 181 if (isHL7) { 182 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_HL7_MISSING_ELEMENT_SHOULD, "hierarchyMeaning"); 183 } else { 184 hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, s.getLiteralPath(), false, I18nConstants.CODESYSTEM_CS_NONHL7_MISSING_ELEMENT, "hierarchyMeaning"); 185 } 186 } 187 } 188 } 189 190 191 private boolean hasHeirarchy(Element cs) { 192 for (Element c : cs.getChildren("concept")) { 193 if (c.hasChildren("concept")) { 194 return true; 195 } 196 } 197 return false; 198 } 199 200 private boolean validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) { 201 String code = concept.getChildValue("code"); 202 if (!Utilities.noString(code)) { 203 org.hl7.fhir.r5.context.IWorkerContext.ValidationResult res = context.validateCode(options, systemFromCanonical(supp), versionFromCanonical(supp), code, null); 204 return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code); 205 } else { 206 return true; 207 } 208 209 } 210 211 private int countConcepts(Element cs) { 212 List<Element> concepts = cs.getChildrenByName("concept"); 213 int res = concepts.size(); 214 for (Element concept : concepts) { 215 res = res + countConcepts(concept); 216 } 217 return res; 218 } 219 220 221}