001package org.hl7.fhir.validation.instance.type; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import org.hl7.fhir.r5.context.IWorkerContext; 007import org.hl7.fhir.r5.context.IWorkerContext.CodingValidationRequest; 008import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 009import org.hl7.fhir.r5.elementmodel.Element; 010import org.hl7.fhir.r5.model.Coding; 011import org.hl7.fhir.r5.model.Resource; 012import org.hl7.fhir.r5.model.ValueSet; 013import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 014import org.hl7.fhir.r5.utils.XVerExtensionManager; 015import org.hl7.fhir.utilities.Utilities; 016import org.hl7.fhir.utilities.VersionUtilities; 017import org.hl7.fhir.utilities.i18n.I18nConstants; 018import org.hl7.fhir.utilities.validation.ValidationMessage; 019import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 020import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 021import org.hl7.fhir.utilities.validation.ValidationOptions; 022import org.hl7.fhir.validation.BaseValidator; 023import org.hl7.fhir.validation.TimeTracker; 024import org.hl7.fhir.validation.instance.InstanceValidator; 025import org.hl7.fhir.validation.instance.utils.NodeStack; 026 027public class ValueSetValidator extends BaseValidator { 028 029 public class VSCodingValidationRequest extends CodingValidationRequest { 030 031 private NodeStack stack; 032 033 public VSCodingValidationRequest(NodeStack stack, Coding code) { 034 super(code); 035 this.stack = stack; 036 } 037 038 public NodeStack getStack() { 039 return stack; 040 } 041 042 } 043 044 private InstanceValidator parent; 045 046 public ValueSetValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager, Coding jurisdiction) { 047 super(context, xverManager); 048 source = Source.InstanceValidator; 049 this.timeTracker = timeTracker; 050 this.parent = parent; 051 this.jurisdiction = jurisdiction; 052 053 } 054 055 public boolean validateValueSet(List<ValidationMessage> errors, Element vs, NodeStack stack) { 056 boolean ok = true; 057 if (!VersionUtilities.isR2Ver(context.getVersion())) { 058 List<Element> composes = vs.getChildrenByName("compose"); 059 int cc = 0; 060 for (Element compose : composes) { 061 ok = validateValueSetCompose(errors, compose, stack.push(compose, cc, null, null), vs.getNamedChildValue("url"), "retired".equals(vs.getNamedChildValue("url"))) & ok; 062 cc++; 063 } 064 } 065 if (!stack.isContained()) { 066 ok = checkShareableValueSet(errors, vs, stack) && ok; 067 } 068 return ok; 069 } 070 071 private boolean checkShareableValueSet(List<ValidationMessage> errors, Element vs, NodeStack stack) { 072 if (parent.isForPublication()) { 073 if (isHL7(vs)) { 074 boolean ok = true; 075 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("url"), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "url") && ok; 076 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("version"), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "version") && ok; 077 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("title"), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "title") && ok; 078 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("name"), I18nConstants.VALUESET_SHAREABLE_EXTRA_MISSING_HL7, "name"); 079 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("status"), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "status") && ok; 080 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("experimental"), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "experimental") && ok; 081 ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("description"), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "description") && ok; 082 return ok; 083 } else { 084 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("url"), I18nConstants.VALUESET_SHAREABLE_MISSING, "url"); 085 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("version"), I18nConstants.VALUESET_SHAREABLE_MISSING, "version"); 086 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("title"), I18nConstants.VALUESET_SHAREABLE_MISSING, "title"); 087 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("name"), I18nConstants.VALUESET_SHAREABLE_EXTRA_MISSING, "name"); 088 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("status"), I18nConstants.VALUESET_SHAREABLE_MISSING, "status"); 089 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("experimental"), I18nConstants.VALUESET_SHAREABLE_MISSING, "experimental"); 090 warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("description"), I18nConstants.VALUESET_SHAREABLE_MISSING, "description"); 091 } 092 } 093 return true; 094 } 095 096 097 private boolean validateValueSetCompose(List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired) { 098 boolean ok = true; 099 List<Element> includes = compose.getChildrenByName("include"); 100 int ci = 0; 101 for (Element include : includes) { 102 ok = validateValueSetInclude(errors, include, stack.push(include, ci, null, null), vsid, retired) && ok; 103 ci++; 104 } 105 List<Element> excludes = compose.getChildrenByName("exclude"); 106 int ce = 0; 107 for (Element exclude : excludes) { 108 ok = validateValueSetInclude(errors, exclude, stack.push(exclude, ce, null, null), vsid, retired) && ok; 109 ce++; 110 } 111 return ok; 112 } 113 114 private boolean validateValueSetInclude(List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired) { 115 boolean ok = true; 116 String system = include.getChildValue("system"); 117 String version = include.getChildValue("version"); 118 List<Element> valuesets = include.getChildrenByName("valueSet"); 119 int i = 0; 120 for (Element ve : valuesets) { 121 String v = ve.getValue(); 122 ValueSet vs = context.fetchResource(ValueSet.class, v); 123 if (vs == null) { 124 NodeStack ns = stack.push(ve, i, ve.getProperty().getDefinition(), ve.getProperty().getDefinition()); 125 126 Resource rs = context.fetchResource(Resource.class, v); 127 if (rs != null) { 128 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.VALUESET_REFERENCE_INVALID_TYPE, v, rs.fhirType()); 129 } else { 130 // todo: it's possible, at this point, that the txx server knows the value set, but it's not in scope 131 // should we handle this case? 132 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.VALUESET_REFERENCE_UNKNOWN, v); 133 } 134 } 135 i++; 136 } 137 if (valuesets.size() > 1) { 138 warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, stack.getLiteralPath(), false, I18nConstants.VALUESET_IMPORT_UNION_INTERSECTION); 139 } 140 List<Element> concepts = include.getChildrenByName("concept"); 141 List<Element> filters = include.getChildrenByName("filter"); 142 if (!Utilities.noString(system)) { 143 boolean systemOk = true; 144 int cc = 0; 145 List<VSCodingValidationRequest> batch = new ArrayList<>(); 146 boolean first = true; 147 for (Element concept : concepts) { 148 // we treat the first differently because we want to know if tbe system is worth validating. if it is, then we batch the rest 149 if (first) { 150 systemOk = validateValueSetIncludeConcept(errors, concept, stack, stack.push(concept, cc, null, null), system, version); 151 first = false; 152 } else if (systemOk) { 153 batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version)); 154 } 155 cc++; 156 } 157 if (parent.isValidateValueSetCodesOnTxServer() && batch.size() > 0) { 158 long t = System.currentTimeMillis(); 159 if (parent.isDebug()) { 160 System.out.println(" : Validate "+batch.size()+" codes from "+system+" for "+vsid); 161 } 162 context.validateCodeBatch(ValidationOptions.defaults(), batch, null); 163 if (parent.isDebug()) { 164 System.out.println(" : .. "+(System.currentTimeMillis()-t)+"ms"); 165 } 166 for (VSCodingValidationRequest cv : batch) { 167 if (version == null) { 168 ok = warningOrHint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, cv.getCoding().getCode()) && ok; 169 } else { 170 ok = warningOrHint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, cv.getCoding().getCode()) && ok; 171 } 172 } 173 } 174 175 int cf = 0; 176 for (Element filter : filters) { 177 if (systemOk && !validateValueSetIncludeFilter(errors, include, stack.push(filter, cf, null, null), system, version)) { 178 systemOk = false; 179 } 180 cf++; 181 } 182 } else { 183 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING); 184 } 185 return ok; 186 } 187 188 private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version) { 189 String code = concept.getChildValue("code"); 190 if (version == null) { 191 ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null), null); 192 if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 193 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_UNC_SYSTEM_WARNING, system, vv.getMessage()); 194 return false; 195 } else { 196 boolean ok = vv.isOk(); 197 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code); 198 } 199 } else { 200 ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null).setVersion(version), null); 201 if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 202 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_UNC_SYSTEM_WARNING_VER, system+"#"+version, vv.getMessage()); 203 return false; 204 } else { 205 boolean ok = vv.isOk(); 206 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code); 207 } 208 } 209 return true; 210 } 211 212 private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version) { 213 String code = concept.getChildValue("code"); 214 Coding c = new Coding(system, code, null); 215 if (version != null) { 216 c.setVersion(version); 217 } 218 return new VSCodingValidationRequest(stack, c); 219 } 220 221 private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element include, NodeStack push, String system, String version) { 222 return true; 223 } 224}