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) { 047 super(context, xverManager); 048 source = Source.InstanceValidator; 049 this.timeTracker = timeTracker; 050 this.parent = parent; 051 } 052 053 public void validateValueSet(List<ValidationMessage> errors, Element vs, NodeStack stack) { 054 if (!VersionUtilities.isR2Ver(context.getVersion())) { 055 List<Element> composes = vs.getChildrenByName("compose"); 056 int cc = 0; 057 for (Element compose : composes) { 058 validateValueSetCompose(errors, compose, stack.push(compose, cc, null, null), vs.getNamedChildValue("url"), "retired".equals(vs.getNamedChildValue("url"))); 059 cc++; 060 } 061 } 062 } 063 064 private void validateValueSetCompose(List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired) { 065 List<Element> includes = compose.getChildrenByName("include"); 066 int ci = 0; 067 for (Element include : includes) { 068 validateValueSetInclude(errors, include, stack.push(include, ci, null, null), vsid, retired); 069 ci++; 070 } 071 List<Element> excludes = compose.getChildrenByName("exclude"); 072 int ce = 0; 073 for (Element exclude : excludes) { 074 validateValueSetInclude(errors, exclude, stack.push(exclude, ce, null, null), vsid, retired); 075 ce++; 076 } 077 } 078 079 private void validateValueSetInclude(List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired) { 080 String system = include.getChildValue("system"); 081 String version = include.getChildValue("version"); 082 List<Element> valuesets = include.getChildrenByName("valueSet"); 083 int i = 0; 084 for (Element ve : valuesets) { 085 String v = ve.getValue(); 086 ValueSet vs = context.fetchResource(ValueSet.class, v); 087 if (vs == null) { 088 NodeStack ns = stack.push(ve, i, ve.getProperty().getDefinition(), ve.getProperty().getDefinition()); 089 090 Resource rs = context.fetchResource(Resource.class, v); 091 if (rs != null) { 092 warning(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.VALUESET_REFERENCE_INVALID_TYPE, v, rs.fhirType()); 093 } else { 094 // todo: it's possible, at this point, that the txx server knows the value set, but it's not in scope 095 // should we handle this case? 096 warning(errors, IssueType.BUSINESSRULE, ns.getLiteralPath(), false, I18nConstants.VALUESET_REFERENCE_UNKNOWN, v); 097 } 098 } 099 i++; 100 } 101 List<Element> concepts = include.getChildrenByName("concept"); 102 List<Element> filters = include.getChildrenByName("filter"); 103 if (!Utilities.noString(system)) { 104 boolean systemOk = true; 105 int cc = 0; 106 List<VSCodingValidationRequest> batch = new ArrayList<>(); 107 boolean first = true; 108 for (Element concept : concepts) { 109 // we treat the first differently because we want to know if tbe system is worth validating. if it is, then we batch the rest 110 if (first) { 111 systemOk = validateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version); 112 first = false; 113 } else if (systemOk) { 114 batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version)); 115 } 116 cc++; 117 } 118 if (parent.isValidateValueSetCodesOnTxServer() && batch.size() > 0) { 119 long t = System.currentTimeMillis(); 120 if (parent.isDebug()) { 121 System.out.println(" : Validate "+batch.size()+" codes from "+system+" for "+vsid); 122 } 123 context.validateCodeBatch(ValidationOptions.defaults(), batch, null); 124 if (parent.isDebug()) { 125 System.out.println(" : .. "+(System.currentTimeMillis()-t)+"ms"); 126 } 127 for (VSCodingValidationRequest cv : batch) { 128 if (version == null) { 129 warningOrHint(errors, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, cv.getCoding().getCode()); 130 } else { 131 warningOrHint(errors, IssueType.BUSINESSRULE, cv.getStack().getLiteralPath(), cv.getResult().isOk(), !retired, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, cv.getCoding().getCode()); 132 } 133 } 134 } 135 136 int cf = 0; 137 for (Element filter : filters) { 138 if (systemOk && !validateValueSetIncludeFilter(errors, include, stack.push(filter, cf, null, null), system, version)) { 139 systemOk = false; 140 } 141 cf++; 142 } 143 warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), systemOk, version == null ? I18nConstants.VALUESET_UNC_SYSTEM_WARNING : I18nConstants.VALUESET_UNC_SYSTEM_WARNING_VER); 144 } else { 145 warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING); 146 } 147 } 148 149 private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version) { 150 String code = concept.getChildValue("code"); 151 if (version == null) { 152 ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null), null); 153 if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 154 return false; 155 } else { 156 boolean ok = vv.isOk(); 157 warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code); 158 } 159 } else { 160 ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null).setVersion(version), null); 161 if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 162 return false; 163 } else { 164 boolean ok = vv.isOk(); 165 warning(errors, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code); 166 } 167 } 168 return true; 169 } 170 171 private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version) { 172 String code = concept.getChildValue("code"); 173 Coding c = new Coding(system, code, null); 174 if (version != null) { 175 c.setVersion(version); 176 } 177 return new VSCodingValidationRequest(stack, c); 178 } 179 180 private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element include, NodeStack push, String system, String version) { 181 return true; 182 } 183}