
001package org.hl7.fhir.r5.terminologies.validation; 002 003import java.io.IOException; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032 */ 033 034import java.util.*; 035 036 037import lombok.extern.slf4j.Slf4j; 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.exceptions.NoTerminologyServiceException; 040import org.hl7.fhir.r5.context.ContextUtilities; 041import org.hl7.fhir.r5.context.IWorkerContext; 042import org.hl7.fhir.r5.elementmodel.LanguageUtils; 043import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 044import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 045import org.hl7.fhir.r5.model.*; 046import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; 047import org.hl7.fhir.r5.model.Enumerations.FilterOperator; 048import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 049import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 050import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 051import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 052import org.hl7.fhir.r5.model.OperationOutcome.IssueType; 053import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; 054import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 055import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 056import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 057import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 058import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 059import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent; 060import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 061import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager; 062import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 063import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; 064import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem; 065import org.hl7.fhir.r5.terminologies.providers.URICodeSystem; 066import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; 067import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException; 068import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass; 069import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 070import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase; 071import org.hl7.fhir.r5.utils.OperationOutcomeUtilities; 072 073import org.hl7.fhir.r5.utils.UserDataNames; 074import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; 075import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationContextResourceProxy; 076import org.hl7.fhir.utilities.*; 077import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference; 078import org.hl7.fhir.utilities.i18n.subtag.LanguageSubtagRegistry; 079import org.hl7.fhir.utilities.i18n.I18nConstants; 080import org.hl7.fhir.utilities.i18n.LanguageTag; 081import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 082import org.hl7.fhir.utilities.validation.ValidationOptions; 083 084@MarkedToMoveToAdjunctPackage 085@Slf4j 086public class ValueSetValidator extends ValueSetProcessBase { 087 088 public static final String NO_TRY_THE_SERVER = "The local terminology server cannot handle this request"; 089 090 091 public class StringWithCodes { 092 private OpIssueCode code; 093 private String message; 094 private String messageId; 095 protected StringWithCodes(OpIssueCode code, String message, String messageId) { 096 super(); 097 this.code = code; 098 this.message = message; 099 this.messageId = messageId; 100 } 101 public OpIssueCode getCode() { 102 return code; 103 } 104 public String getMessage() { 105 return message; 106 } 107 public String getMessageId() {return messageId;} 108 } 109 110 private ValueSet valueset; 111 private Map<String, ValueSetValidator> inner = new HashMap<>(); 112 private ValidationOptions options; 113 private ValidationContextCarrier localContext; 114 private List<CodeSystem> localSystems = new ArrayList<>(); 115 protected Parameters expansionParameters; 116 private TerminologyClientManager tcm; 117 private Set<String> unknownSystems; 118 private Set<String> unknownValueSets = new HashSet<>(); 119 private boolean throwToServer; 120 private LanguageSubtagRegistry registry; 121 122 123 public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, Parameters expansionProfile, TerminologyClientManager tcm, LanguageSubtagRegistry registry) { 124 super(context, opContext); 125 this.valueset = source; 126 this.options = options; 127 this.expansionParameters = expansionProfile; 128 this.tcm = tcm; 129 this.registry = registry; 130 analyseValueSet(); 131 } 132 133 public ValueSetValidator(IWorkerContext context, TerminologyOperationContext opContext, ValidationOptions options, ValueSet source, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyClientManager tcm, LanguageSubtagRegistry registry) { 134 super(context, opContext); 135 this.valueset = source; 136 this.options = options.copy(); 137 this.options.setEnglishOk(true); 138 this.localContext = ctxt; 139 this.expansionParameters = expansionProfile; 140 this.tcm = tcm; 141 this.registry = registry; 142 analyseValueSet(); 143 } 144 145 public Set<String> getUnknownSystems() { 146 return unknownSystems; 147 } 148 149 public void setUnknownSystems(Set<String> unknownSystems) { 150 this.unknownSystems = unknownSystems; 151 } 152 153 public boolean isThrowToServer() { 154 return throwToServer; 155 } 156 157 public void setThrowToServer(boolean throwToServer) { 158 this.throwToServer = throwToServer; 159 } 160 161 private void analyseValueSet() { 162 opContext.note("analyse"); 163 if (valueset != null) { 164 opContext.note("vs = "+valueset.getVersionedUrl()); 165 opContext.seeContext(valueset.getVersionedUrl()); 166 for (Extension s : valueset.getExtensionsByUrl(ExtensionDefinitions.EXT_VS_CS_SUPPL_NEEDED)) { 167 requiredSupplements.add(s.getValue().primitiveValue()); 168 } 169 170 if (!requiredSupplements.isEmpty()) { 171 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 172 if (inc.hasSystem()) { 173 checkCodeSystemResolves(inc); 174 } 175 } 176 for (ConceptSetComponent inc : valueset.getCompose().getExclude()) { 177 if (inc.hasSystem()) { 178 checkCodeSystemResolves(inc); 179 } 180 } 181 } 182 } else { 183 opContext.note("vs = null"); 184 } 185 186 altCodeParams.seeParameters(expansionParameters); 187 altCodeParams.seeValueSet(valueset); 188 if (localContext != null) { 189 if (valueset != null) { 190 for (ConceptSetComponent i : valueset.getCompose().getInclude()) { 191 analyseComponent(i, "inc"+i); 192 } 193 for (ConceptSetComponent i : valueset.getCompose().getExclude()) { 194 analyseComponent(i, "exc"+i); 195 } 196 } 197 } 198 opContext.note("analysed"); 199 } 200 201 private void checkCodeSystemResolves(ConceptSetComponent c) { 202 VersionInfo vi = new VersionInfo(this); 203 CodeSystem cs = resolveCodeSystem(c.getSystem(), vi.getVersion(c.getSystem(), c.getVersion())); 204 if (cs == null) { 205 // well, it doesn't really matter at this point. Mainly we're triggering the supplement analysis to happen 206 opContext.note("Unable to resolve "+c.getSystem()+"#"+vi.getVersion(c.getSystem(), c.getVersion())); 207 } 208 } 209 210 private void analyseComponent(ConceptSetComponent i, String name) { 211 opContext.deadCheck("analyse Component "+name); 212 if (i.getSystemElement().hasExtension(ExtensionDefinitions.EXT_VALUESET_SYSTEM)) { 213 String ref = i.getSystemElement().getExtensionString(ExtensionDefinitions.EXT_VALUESET_SYSTEM); 214 if (ref.startsWith("#")) { 215 String id = ref.substring(1); 216 for (ValidationContextResourceProxy t : localContext.getResources()) { 217 CodeSystem cs = (CodeSystem) t.loadContainedResource(id, CodeSystem.class); 218 if (cs != null) { 219 localSystems.add(cs); 220 } 221 } 222 } else { 223 throw new Error("Not done yet #2: "+ref); 224 } 225 } 226 } 227 228 public ValidationResult validateCode(CodeableConcept code) throws FHIRException { 229 return validateCode("CodeableConcept", code); 230 } 231 232 public ValidationResult validateCode(String path, CodeableConcept code) throws FHIRException { 233 opContext.deadCheck("validate "+code.toString()); 234 checkValueSetOptions(); 235 236 // first, we validate the codings themselves 237 ValidationProcessInfo info = new ValidationProcessInfo(); 238 239 if (throwToServer) { 240 checkValueSetLoad(info); 241 } 242 243 CodeableConcept vcc = new CodeableConcept(); 244 List<ValidationResult> resList = new ArrayList<>(); 245 246 if (!options.isMembershipOnly()) { 247 int i = 0; 248 for (Coding c : code.getCoding()) { 249 if (!c.hasSystem() && !c.hasUserData(UserDataNames.tx_val_sys_error)) { 250 c.setUserData(UserDataNames.tx_val_sys_error, true); 251 info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".coding["+i+"]", 252 context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE), OpIssueCode.InvalidData, null, I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); 253 } else { 254 VersionInfo vi = new VersionInfo(this); 255 checkExpansion(c, vi); 256 checkInclude(c, vi); 257 CodeSystem cs = resolveCodeSystem(c.getSystem(), vi.getVersion(c.getSystem(), c.getVersion())); 258 ValidationResult res = null; 259 if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.SUPPLEMENT)) { 260 if (context.isNoTerminologyServer()) { 261 if (c.hasVersion()) { 262 String msg = getUnknownCodeSystemMessage(c.getSystem(), c.getVersion()); 263 res = new ValidationResult(IssueSeverity.ERROR, msg, 264 makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg, OpIssueCode.NotFound, null, getUnknownCodeSystemMessageId(c.getSystem(), c.getVersion()))).setUnknownSystems(unknownSystems); 265 } else { 266 String msg = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, c.getSystem(), c.getVersion()); 267 unknownSystems.add(c.getSystem()); 268 res = new ValidationResult(IssueSeverity.ERROR, msg, 269 makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg, OpIssueCode.NotFound, null, I18nConstants.UNKNOWN_CODESYSTEM)).setUnknownSystems(unknownSystems); 270 } 271 } else { 272 res = context.validateCode(options.withNoClient(), c, null); 273 if (res.isOk()) { 274 vcc.addCoding(new Coding().setCode(res.getCode()).setVersion(res.getVersion()).setSystem(res.getSystem()).setDisplay(res.getDisplay())); 275 } 276 for (OperationOutcomeIssueComponent iss : res.getIssues()) { 277 if (iss.getSeverity() == org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR && iss.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-found")) { 278 iss.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING); 279 res.setSeverity(IssueSeverity.WARNING); 280 } 281 iss.resetPath("Coding", path+".coding["+i+"]"); 282 } 283 if (res.isInactive()) { 284 String status = res.getStatus(); 285 if (status == null) { 286 status = "inactive"; 287 } 288 String msg = context.formatMessage(I18nConstants.INACTIVE_CONCEPT_FOUND, status, c.getCode()); 289 res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.BUSINESSRULE, path, msg, OpIssueCode.CodeComment, res.getServer(), I18nConstants.INACTIVE_CONCEPT_FOUND)); 290 } 291 } 292 } else if (cs.getContent() == CodeSystemContentMode.SUPPLEMENT || cs.hasSupplements()) { 293 String msg = context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getVersionedUrl()); 294 res = new ValidationResult(IssueSeverity.ERROR, msg, 295 makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path+".coding["+i+"].system", msg, OpIssueCode.InvalidData, null, I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT)); 296 } else { 297 c.setUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM, cs); 298 299 checkCanonical(info.getIssues(), path, cs, valueset); 300 res = validateCode(path+".coding["+i+"]", c, cs, vcc, info); 301 } 302 info.getIssues().addAll(res.getIssues()); 303 if (res != null) { 304 resList.add(res); 305 if (!res.isOk() && !res.messageIsInIssues()) { 306 // no message ids here 307 if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 308 info.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.NOTFOUND, path+".coding["+i+"]", res.getMessage(), OpIssueCode.NotFound, res.getServer(), null)); 309 } else { 310 info.getIssues().addAll(makeIssue(res.getSeverity(), IssueType.CODEINVALID, path+".coding["+i+"]", res.getMessage(), OpIssueCode.InvalidCode, res.getServer(), null)); 311 } 312 } 313 } 314 } 315 i++; 316 } 317 } 318 Coding foundCoding = null; 319 String msg = null; 320 Boolean result = false; 321 if (valueset != null) { 322 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", "); 323 List<String> cpath = new ArrayList<>(); 324 325 int i = 0; 326 for (Coding c : code.getCoding()) { 327 String cs = "'"+c.getSystem()+(c.hasVersion() ? "|"+c.getVersion() : "")+"#"+c.getCode()+(c.hasDisplay() ? " ('"+c.getDisplay()+"')" : "")+"'"; 328 String cs2 = c.getSystem()+(c.hasVersion() ? "|"+c.getVersion() : ""); 329 cpath.add(path+".coding["+i+"]"); 330 b.append(cs2); 331 Boolean ok = codeInValueSet(path, c.getSystem(), c.getVersion(), c.getCode(), info); 332 if (ok == null && result != null && result == false) { 333 result = null; 334 } else if (ok != null && ok) { 335 result = true; 336 foundCoding = c; 337 if (!options.isMembershipOnly()) { 338 vcc.addCoding().setSystem(c.getSystem()).setVersion(c.getVersion()).setCode(c.getCode()); 339 } 340 } 341 if (ok == null || !ok) { 342 vcc.removeCoding(c.getSystem(), c.getVersion(), c.getCode()); 343 } 344 if (ok != null && !ok) { 345 msg = context.formatMessage(I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ONE, null, valueset.getVersionedUrl(), cs); 346 info.getIssues().addAll(makeIssue(IssueSeverity.INFORMATION, IssueType.CODEINVALID, path+".coding["+i+"].code", msg, OpIssueCode.ThisNotInVS, null, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ONE)); 347 } 348 i++; 349 } 350 if (result == null) { 351 String msgid = null; 352 if (!unknownValueSets.isEmpty()) { 353 msgid = I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_VS; 354 msg = context.formatMessage(msgid, valueset.getVersionedUrl(), CommaSeparatedStringBuilder.join(", ", unknownValueSets)); 355 } else { 356 msgid = I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_CS; 357 msg = context.formatMessage(msgid, valueset.getVersionedUrl(), b.toString()); 358 } 359 info.getIssues().addAll(makeIssue(IssueSeverity.WARNING, unknownSystems.isEmpty() && unknownValueSets.isEmpty() ? IssueType.CODEINVALID : IssueType.NOTFOUND, null, msg, OpIssueCode.VSProcessing, null, msgid)); 360 } else if (!result) { 361 // to match Ontoserver 362 OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, org.hl7.fhir.r5.model.OperationOutcome.IssueType.CODEINVALID); 363 iss.getDetails().setText(context.formatMessage(I18nConstants.TX_GENERAL_CC_ERROR_MESSAGE, valueset.getVersionedUrl())); 364 iss.getDetails().addCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", OpIssueCode.NotInVS.toCode(), null); 365 info.getIssues().add(iss); 366 367// msg = context.formatMessagePlural(code.getCoding().size(), I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), b.toString()); 368// info.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, code.getCoding().size() == 1 ? path+".coding[0].code" : path, msg)); 369 } 370 } 371 372 if (vcc.hasCoding() && code.hasText()) { 373 vcc.setText(code.getText()); 374 } 375 if (!checkRequiredSupplements(info)) { 376 return new ValidationResult(IssueSeverity.ERROR, info.getIssues().get(info.getIssues().size()-1).getDetails().getText(), info.getIssues()); 377 } else if (info.hasErrors()) { 378 ValidationResult res = new ValidationResult(IssueSeverity.ERROR, info.summary(), info.getIssues()); 379 if (foundCoding != null) { 380 ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); 381 cd.setDisplay(lookupDisplay(foundCoding)); 382 res.setDefinition(cd); 383 res.setSystem(foundCoding.getSystem()); 384 res.setVersion(foundCoding.hasVersion() ? foundCoding.getVersion() : foundCoding.hasUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM) ? ((CodeSystem) foundCoding.getUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM)).getVersion() : null); 385 res.setDisplay(cd.getDisplay()); 386 } 387 if (info.getErr() != null) { 388 res.setErrorClass(info.getErr()); 389 } 390 res.setUnknownSystems(unknownSystems); 391 res.addCodeableConcept(vcc); 392 return res; 393 } else if (result == null) { 394 return new ValidationResult(IssueSeverity.WARNING, info.summary(), info.getIssues()); 395 } else if (foundCoding == null && valueset != null) { 396 return new ValidationResult(IssueSeverity.ERROR, "Internal Error that should not happen", 397 makeIssue(IssueSeverity.FATAL, IssueType.EXCEPTION, path, "Internal Error that should not happen", OpIssueCode.VSProcessing, null, null)); 398 } else if (info.getIssues().size() > 0) { 399 if (foundCoding == null) { 400 IssueSeverity lvl = IssueSeverity.INFORMATION; 401 for (OperationOutcomeIssueComponent iss : info.getIssues()) { 402 lvl = IssueSeverity.max(lvl, OperationOutcomeUtilities.convert(iss.getSeverity())); 403 } 404 return new ValidationResult(lvl, info.summary(), info.getIssues()); 405 } else { 406 String disp = lookupDisplay(foundCoding); 407 ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); 408 cd.setDisplay(disp); 409 return new ValidationResult(IssueSeverity.WARNING, info.summaryList(), foundCoding.getSystem(), getVersion(foundCoding), cd, disp, info.getIssues()).addCodeableConcept(vcc); 410 } 411 } else if (!result) { 412 if (valueset != null) { 413 throw new Error("This should never happen: no result but is value set"); 414 } else if (vcc.hasCoding()) { 415 return new ValidationResult(vcc.getCodingFirstRep().getSystem(), getVersion(vcc.getCodingFirstRep()), new ConceptDefinitionComponent(vcc.getCodingFirstRep().getCode()).setDisplay(vcc.getCodingFirstRep().getDisplay()), vcc.getCodingFirstRep().getDisplay()).addCodeableConcept(vcc); 416 } else { 417 throw new Error("This should never happen: no result, no value set, no coding"); 418 } 419 } else if (foundCoding != null) { 420 ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); 421 cd.setDisplay(lookupDisplay(foundCoding)); 422 return new ValidationResult(foundCoding.getSystem(), getVersion(foundCoding), cd, getPreferredDisplay(cd, null)).addCodeableConcept(vcc); 423 } else { 424 throw new Error("This should never happen - ther response from the server could not be understood"); 425 } 426 } 427 428 private String getUnknownCodeSystemMessage(String system, String version) { 429 Set<String> set = resolveCodeSystemVersions(system); 430 String msg; 431 if (set.isEmpty()) { 432 msg = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM_VERSION_NONE, system, version); 433 unknownSystems.add(system); 434 } else { 435 msg = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM_VERSION, system, version, CommaSeparatedStringBuilder.join(",", Utilities.sorted(set))); 436 unknownSystems.add(system + "|" + version); 437 } 438 return msg; 439 } 440 441 private String getUnknownCodeSystemMessageId(String system, String version) { 442 Set<String> set = resolveCodeSystemVersions(system); 443 if (set.isEmpty()) { 444 return I18nConstants.UNKNOWN_CODESYSTEM_VERSION_NONE; 445 } else { 446 return I18nConstants.UNKNOWN_CODESYSTEM_VERSION; 447 } 448 } 449 450 private void checkValueSetLoad(ValidationProcessInfo info) { 451 int serverCount = getServerLoad(info); 452 // There's a trade off here: if we're going to hit the server inside the components, then 453 // the amount of value set collateral we send is limited, but we pay the price of hitting 454 // the server multiple times. If, on the other hand, we give up on that, and hit the server 455 // directly, we have to send value set collateral (though we cache at the higher level) 456 // 457 // the cutoff value is chosen experimentally 458 if (serverCount > 2) { 459 throw new VSCheckerException("This value set is better processed on the server for performance reasons", null, true); 460 } 461 } 462 463 private int getServerLoad(ValidationProcessInfo info) { 464 int serverCount = 0; 465 if (valueset != null) { 466 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 467 serverCount = serverCount + checkValueSetLoad(inc, info); 468 } 469 for (ConceptSetComponent inc : valueset.getCompose().getExclude()) { 470 serverCount = serverCount + checkValueSetLoad(inc, info); 471 } 472 } 473 return serverCount; 474 } 475 476 private int checkValueSetLoad(ConceptSetComponent inc, ValidationProcessInfo info) { 477 int serverCount = 0; 478 for (UriType uri : inc.getValueSet()) { 479 String url = getCu().pinValueSet(uri.getValue(), expansionParameters); 480 ValueSetValidator vsv = getVs(url, info); 481 serverCount += vsv.getServerLoad(info); 482 } 483 if (inc.hasSystem()) { 484 CodeSystem cs = resolveCodeSystem(inc.getSystem(), inc.getVersion()); 485 if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)) { 486 serverCount++; 487 } 488 } 489 return serverCount; 490 } 491 492 private boolean checkRequiredSupplements(ValidationProcessInfo info) { 493 if (!requiredSupplements.isEmpty()) { 494 String msg = context.formatMessagePlural(requiredSupplements.size(), I18nConstants.VALUESET_SUPPLEMENT_MISSING, CommaSeparatedStringBuilder.build(requiredSupplements)); 495 throw new TerminologyServiceProtectionException(msg, TerminologyServiceErrorClass.BUSINESS_RULE, IssueType.NOTFOUND); 496 } 497 return requiredSupplements.isEmpty(); 498 } 499 500 private boolean valueSetDependsOn(String system, String version) { 501 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 502 if (system.equals(inc.getSystem()) && (version == null || inc.getVersion() == null || version.equals(inc.getVersion()))) { 503 return true; 504 } 505 } 506 return false; 507 } 508 509 private String getVersion(Coding c) { 510 if (c.hasVersion()) { 511 return c.getVersion(); 512 } else if (c.hasUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM)) { 513 return ((CodeSystem) c.getUserData(UserDataNames.TX_ASSOCIATED_CODESYSTEM)).getVersion(); 514 } else { 515 return null; 516 } 517 } 518 519 private String lookupDisplay(Coding c) { 520 CodeSystem cs = resolveCodeSystem(c.getSystem(), c.getVersion()); 521 if (cs != null) { 522 ConceptDefinitionComponent cd = CodeSystemUtilities.findCodeOrAltCode(cs.getConcept(), c.getCode(), null); 523 if (cd != null) { 524 return getPreferredDisplay(cd, cs); 525 } 526 } 527 return null; 528 } 529 530 public CodeSystem resolveCodeSystem(String system, String version) { 531 for (CodeSystem t : localSystems) { 532 if (t.getUrl().equals(system) && versionsMatch(version, t.getVersion())) { 533 return t; 534 } 535 } 536 CodeSystem cs = context.fetchSupplementedCodeSystem(system, version); 537 if (cs == null) { 538 cs = findSpecialCodeSystem(system, version); 539 } 540 if (cs == null) { 541 cs = context.findTxResource(CodeSystem.class, system, version); 542 } 543 if (cs != null) { 544 if (cs.hasUserData("supplements.installed")) { 545 for (String s : cs.getUserString("supplements.installed").split("\\,")) { 546 s = removeSupplement(s); 547 } 548 } 549 } 550 return cs; 551 } 552 553 public Set<String> resolveCodeSystemVersions(String system) { 554 Set<String> res = new HashSet<>(); 555 for (CodeSystem t : localSystems) { 556 if (t.getUrl().equals(system) && t.hasVersion()) { 557 res.add(t.getVersion()); 558 } 559 } 560 res.addAll(new ContextUtilities(context).fetchCodeSystemVersions(system)); 561 return res; 562 } 563 564 private boolean versionsMatch(String versionTest, String versionActual) { 565 return versionTest == null || versionActual == null || VersionUtilities.versionMatches(versionTest, versionActual); 566 } 567 568 public ValidationResult validateCode(Coding code) throws FHIRException { 569 return validateCode("Coding", code); 570 } 571 572 public ValidationResult validateCode(String path, Coding code) throws FHIRException { 573 opContext.deadCheck("validate "+code.toString()); 574 checkValueSetOptions(); 575 576 String warningMessage = null; 577 // first, we validate the concept itself 578 579 ValidationResult res = null; 580 boolean inExpansion = false; 581 boolean inInclude = false; 582 List<OperationOutcomeIssueComponent> issues = new ArrayList<>(); 583 ValidationProcessInfo info = new ValidationProcessInfo(issues); 584 VersionInfo vi = new VersionInfo(this); 585 checkCanonical(issues, path, valueset, valueset); 586 587 String system = code.getSystem(); 588 if (!options.isMembershipOnly()) { 589 if (system == null && !code.hasDisplay() && options.isGuessSystem()) { // dealing with just a plain code (enum) 590 List<StringWithCodes> problems = new ArrayList<>(); 591 system = systemForCodeInValueSet(code.getCode(), problems); 592 if (system == null) { 593 if (problems.size() == 0) { 594 throw new Error("Unable to resolve systems but no reason why"); // this is an error in the java code 595 } else if (problems.size() == 1) { 596 String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), "'"+code.toString()+"'"); 597 issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, "code", msg, OpIssueCode.NotInVS, null, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ONE)); 598 issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, "code", problems.get(0).getMessage(), problems.get(0).getCode(), null, problems.get(0).getMessageId())); 599 return new ValidationResult(IssueSeverity.ERROR, problems.get(0).getMessage()+"; "+msg, issues); 600 } else { 601 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; "); 602 for (StringWithCodes s : problems) { 603 b.append(s.getMessage()); 604 } 605 ValidationResult vr = new ValidationResult(IssueSeverity.ERROR, b.toString(), null); 606 for (StringWithCodes s : problems) { 607 vr.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.UNKNOWN, path, s.getMessage(), s.getCode(), vr.getServer(), s.getMessageId())); 608 } 609 return vr; 610 } 611 } 612 } 613 if (!code.hasSystem()) { 614 if (options.isGuessSystem() && system == null && Utilities.isAbsoluteUrl(code.getCode())) { 615 system = "urn:ietf:rfc:3986"; // this arises when using URIs bound to value sets 616 } 617 code.setSystem(system); 618 } 619 if (!code.hasSystem()) { 620 res = new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE), null); 621 if (!code.hasUserData(UserDataNames.tx_val_sys_error)) { 622 code.setUserData(UserDataNames.tx_val_sys_error, true); 623 res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path, context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE), OpIssueCode.InvalidData, null, I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); 624 } 625 } else { 626 if (!Utilities.isAbsoluteUrl(system)) { 627 String msg = context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE); 628 issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path+".system", msg, OpIssueCode.InvalidData, null, I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE)); 629 } 630 inExpansion = checkExpansion(code, vi); 631 inInclude = checkInclude(code, vi); 632 String wv = vi.getVersion(system, code.getVersion()); 633 CodeSystem cs = resolveCodeSystem(system, wv); 634 if (cs == null) { 635 if (!VersionUtilities.isR6Plus(context.getVersion()) && "urn:ietf:bcp:13".equals(system) && Utilities.existsInList(code.getCode(), "xml", "json", "ttl") && "http://hl7.org/fhir/ValueSet/mimetypes".equals(valueset.getUrl())) { 636 return new ValidationResult(system, null, new ConceptDefinitionComponent(code.getCode()), "application/fhir+"+code.getCode()); 637 } else { 638 OpIssueCode oic = OpIssueCode.NotFound; 639 IssueType itype = IssueType.NOTFOUND; 640 ValueSet vs = context.fetchResource(ValueSet.class, system); 641 String msgid = null; 642 if (vs != null) { 643 msgid = I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2; 644 warningMessage = context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system); 645 oic = OpIssueCode.InvalidData; 646 itype = IssueType.INVALID; 647 } else if (wv == null) { 648 msgid = I18nConstants.UNKNOWN_CODESYSTEM; 649 warningMessage = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, system); 650 unknownSystems.add(system); 651 } else { 652 msgid = getUnknownCodeSystemMessageId(system, wv); 653 warningMessage = getUnknownCodeSystemMessage(system, wv); 654 } 655 if (!inExpansion) { 656 if (valueset != null && valueset.hasExpansion()) { 657 String msg = context.formatMessage(I18nConstants.CODESYSTEM_CS_UNK_EXPANSION, 658 valueset.getUrl(), 659 code.getSystem(), 660 code.getCode().toString()); 661 issues.addAll(makeIssue(IssueSeverity.ERROR, itype, path, msg, OpIssueCode.VSProcessing, null, I18nConstants.CODESYSTEM_CS_UNK_EXPANSION)); 662 throw new VSCheckerException(msg, issues, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 663 } else { 664 issues.addAll(makeIssue(IssueSeverity.ERROR, itype, path+".system", warningMessage, oic, null, msgid)); 665 res = new ValidationResult(IssueSeverity.WARNING, warningMessage, issues); 666 if (valueset == null) { 667 throw new VSCheckerException(warningMessage, issues, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 668 } else { 669 // String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl(), code.toString()); 670 // issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, msg)); 671 // we don't do this yet 672 // throw new VSCheckerException(warningMessage, issues); 673 } 674 } 675 } 676 } 677 } else { 678 checkCanonical(issues, path, cs, valueset); 679 } 680 if (cs != null && (cs.hasSupplements() || cs.getContent() == CodeSystemContentMode.SUPPLEMENT)) { 681 String msg = context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getVersionedUrl()); 682 return new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path+".system", msg, OpIssueCode.InvalidData, null, I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT)); 683 } 684 if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) { 685 warningMessage = "Resolved system "+system+(cs.hasVersion() ? " (v"+cs.getVersion()+")" : "")+", but the definition "; 686 switch (cs.getContent()) { 687 case EXAMPLE: 688 warningMessage = warningMessage +"only has example content"; 689 break; 690 case FRAGMENT: 691 warningMessage = warningMessage + "is only a fragment"; 692 break; 693 case NOTPRESENT: 694 warningMessage = warningMessage + "doesn't include any codes"; 695 break; 696 case SUPPLEMENT: 697 warningMessage = warningMessage + " is for a supplement to "+cs.getSupplements(); 698 break; 699 default: 700 break; 701 } 702 warningMessage = warningMessage + ", so the code has not been validated"; 703 if (cs.getContent() == CodeSystemContentMode.NOTPRESENT) { 704 throw new VSCheckerException(warningMessage, null, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 705 } 706 if (!options.isExampleOK() && !inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment 707 throw new VSCheckerException(warningMessage, null, true); 708 } 709 } 710 711 if (cs != null /*&& (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)*/) { 712 if (!(cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT || 713 (options.isExampleOK() && cs.getContent() == CodeSystemContentMode.EXAMPLE))) { 714 if (inInclude) { 715 ConceptReferenceComponent cc = findInInclude(code); 716 if (cc != null) { 717 // we'll take it on faith 718 String disp = getPreferredDisplay(cc); 719 res = new ValidationResult(system, cs.getVersion(), new ConceptDefinitionComponent().setCode(cc.getCode()).setDisplay(disp), disp); 720 res.addMessage("Resolved system "+system+", but the definition is not complete, so assuming value set include is correct"); 721 return res; 722 } 723 } 724 // we can't validate that here. 725 throw new FHIRException("Unable to evaluate based on code system with status = "+cs.getContent().toCode()); 726 } 727 res = validateCode(path, code, cs, null, info); 728 res.setIssues(issues); 729 } else if (cs == null && valueset.hasExpansion() && inExpansion) { 730 for (ValueSetExpansionParameterComponent p : valueset.getExpansion().getParameter()) { 731 if ("used-supplement".equals(p.getName())) { 732 removeSupplement(p.getValue().primitiveValue()); 733 } 734 } 735 // we just take the value set as face value then 736 res = new ValidationResult(system, wv, new ConceptDefinitionComponent().setCode(code.getCode()).setDisplay(code.getDisplay()), code.getDisplay()); 737 if (!preferServerSide(system)) { 738 res.addMessage("Code System unknown, so assuming value set expansion is correct ("+warningMessage+")"); 739 } 740 } else { 741 // well, we didn't find a code system - try the expansion? 742 // disabled waiting for discussion 743 if (throwToServer) { 744 throw new FHIRException(NO_TRY_THE_SERVER); 745 } 746 } 747 } 748 } else { 749 inExpansion = checkExpansion(code, vi); 750 inInclude = checkInclude(code, vi); 751 } 752 String wv = vi.getVersion(system, code.getVersion()); 753 if (!checkRequiredSupplements(info)) { 754 return new ValidationResult(IssueSeverity.ERROR, issues.get(issues.size()-1).getDetails().getText(), issues); 755 } 756 757 758 // then, if we have a value set, we check it's in the value set 759 if (valueset != null) { 760 if ((res==null || res.isOk())) { 761 Boolean ok = codeInValueSet(path, system, wv, code.getCode(), info); 762 if (ok == null || !ok) { 763 if (res == null) { 764 res = new ValidationResult((IssueSeverity) null, null, info.getIssues()); 765 } 766 if (info.getErr() != null) { 767 res.setErrorClass(info.getErr()); 768 } 769 if (ok == null) { 770 String m = null; 771 String msgid = null; 772 if (!unknownSystems.isEmpty()) { 773 msgid = I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_CS; 774 m = context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_CS, valueset.getVersionedUrl(), CommaSeparatedStringBuilder.join(",", unknownSystems)); 775 } else if (!unknownValueSets.isEmpty()) { 776 msgid = I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_VS; 777 res.addMessage(info.getIssues().get(0).getDetails().getText()); 778 m = context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_VS, valueset.getVersionedUrl(), CommaSeparatedStringBuilder.join(",", unknownValueSets)); 779 } else { 780 // not sure why we'd get to here? 781 msgid = I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_; 782 m = context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl()); 783 } 784 res.addMessage(m); 785 res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.NOTFOUND, null, m, OpIssueCode.VSProcessing, null, msgid)); 786 res.setUnknownSystems(unknownSystems); 787 res.setSeverity(IssueSeverity.ERROR); // back patching for display logic issue 788 res.setErrorClass(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 789 } else if (!inExpansion && !inInclude) { 790// if (!info.getIssues().isEmpty()) { 791// res.setMessage("Not in value set "+valueset.getUrl()+": "+info.summary()).setSeverity(IssueSeverity.ERROR); 792// res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path, res.getMessage())); 793// } else 794// { 795 String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), "'"+code.toString()+"'"); 796 res.addMessage(msg).setSeverity(IssueSeverity.ERROR); 797 res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path+".code", msg, OpIssueCode.NotInVS, null, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ONE)); 798 res.setDefinition(null); 799 res.setSystem(null); 800 res.setDisplay(null); 801 res.setUnknownSystems(unknownSystems); 802// } 803 } else if (warningMessage!=null) { 804 String msg = context.formatMessage(I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_, warningMessage); 805 res = new ValidationResult(IssueSeverity.WARNING, msg, makeIssue(IssueSeverity.WARNING, IssueType.EXCEPTION, path, msg, OpIssueCode.VSProcessing, null, I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_)); 806 } else if (inExpansion) { 807 res.setMessage("Code found in expansion, however: " + res.getMessage()); 808 res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.EXCEPTION, path, res.getMessage(), OpIssueCode.VSProcessing, null, null)); // no message id? 809 } else if (inInclude) { 810 res.setMessage("Code found in include, however: " + res.getMessage()); 811 res.getIssues().addAll(makeIssue(IssueSeverity.WARNING, IssueType.EXCEPTION, path, res.getMessage(), OpIssueCode.VSProcessing, null, null)); // no message id? 812 } 813 } else if (res == null) { 814 res = new ValidationResult(system, wv, null, null); 815 } 816 } else if ((res != null && !res.isOk())) { 817 String msg = context.formatMessagePlural(1, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getVersionedUrl(), "'"+code.toString()+"'"); 818 res.addMessage(msg); 819 res.getIssues().addAll(makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path+".code", msg, OpIssueCode.NotInVS, null, I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_ONE)); 820 } 821 } 822 if (res != null && res.getSeverity() == IssueSeverity.INFORMATION && res.getMessage() != null) { 823 res.setSeverity(IssueSeverity.ERROR); // back patching for display logic issue 824 } 825 return res; 826 } 827 828 private void checkValueSetOptions() { 829 if (valueset != null) { 830 for (Extension ext : valueset.getCompose().getExtensionsByUrl(ExtensionDefinitions.EXT_VS_EXP_PARAM_NEW, ExtensionDefinitions.EXT_VS_EXP_PARAM_OLD)) { 831 var name = ext.getExtensionString("name"); 832 var value = ext.getExtensionByUrl("value").getValue(); 833 if ("displayLanguage".equals(name)) { 834 options.setLanguages(value.primitiveValue()); 835 } 836 } 837 if (!options.hasLanguages() && valueset.hasLanguage()) { 838 options.addLanguage(valueset.getLanguage()); 839 } 840 } 841 842 if (options.getLanguages() != null) { 843 for (LanguagePreference t : options.getLanguages().getLangs()) { 844 try { 845 LanguageTag tag = new LanguageTag(registry, t.getLang()); 846 } catch (Exception e) { 847 throw new TerminologyServiceProtectionException(context.formatMessage(I18nConstants.INVALID_DISPLAY_NAME, options.getLanguages().getSource()), TerminologyServiceErrorClass.PROCESSING, IssueType.PROCESSING, e.getMessage()); 848 } 849 } 850 } 851 } 852 853 private static final Set<String> SERVER_SIDE_LIST = new HashSet<>(Arrays.asList("http://fdasis.nlm.nih.gov", "http://hl7.org/fhir/sid/ndc", "http://loinc.org", "http://snomed.info/sct", "http://unitsofmeasure.org", 854 "http://unstats.un.org/unsd/methods/m49/m49.htm", "http://varnomen.hgvs.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "https://www.usps.com/", 855 "urn:ietf:bcp:13","urn:ietf:bcp:47","urn:ietf:rfc:3986", "urn:iso:std:iso:3166","urn:iso:std:iso:4217", "urn:oid:1.2.36.1.2001.1005.17")); 856 857 private boolean preferServerSide(String system) { 858 if (SERVER_SIDE_LIST.contains(system)) { 859 return true; 860 } 861 862 try { 863 if (tcm.supportsSystem(system)) { 864 return true; 865 } 866 } catch (IOException e) { 867 e.printStackTrace(); 868 } 869 870 return false; 871 } 872 873 private boolean checkInclude(Coding code, VersionInfo vi) { 874 if (valueset == null || code.getSystem() == null || code.getCode() == null) { 875 return false; 876 } 877 for (ConceptSetComponent inc : valueset.getCompose().getExclude()) { 878 if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { 879 for (ConceptReferenceComponent cc : inc.getConcept()) { 880 if (cc.hasCode() && cc.getCode().equals(code.getCode())) { 881 return false; 882 } 883 } 884 } 885 } 886 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 887 if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { 888 vi.setComposeVersion(inc.getVersion()); 889 for (ConceptReferenceComponent cc : inc.getConcept()) { 890 if (cc.hasCode() && cc.getCode().equals(code.getCode())) { 891 return true; 892 } 893 } 894 } 895 } 896 return false; 897 } 898 899 private ConceptReferenceComponent findInInclude(Coding code) { 900 if (valueset == null || code.getSystem() == null || code.getCode() == null) { 901 return null; 902 } 903 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 904 if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { 905 for (ConceptReferenceComponent cc : inc.getConcept()) { 906 if (cc.hasCode() && cc.getCode().equals(code.getCode())) { 907 return cc; 908 } 909 } 910 } 911 } 912 return null; 913 } 914 915 private CodeSystem findSpecialCodeSystem(String system, String version) { 916 if ("urn:ietf:rfc:3986".equals(system)) { 917 CodeSystem cs = new CodeSystem(); 918 cs.setUrl(system); 919 cs.setUserData(UserDataNames.tx_cs_special, new URICodeSystem()); 920 cs.setContent(CodeSystemContentMode.COMPLETE); 921 return cs; 922 } 923 if (Utilities.isAbsoluteUrl(system)) { 924 StructureDefinition sd = context.fetchResource(StructureDefinition.class, system, version); 925 if (sd != null) { 926 return CodeSystemUtilities.convertSD(sd); 927 } 928 } 929 return null; 930 } 931 932 private ValidationResult findCodeInExpansion(Coding code) { 933 if (valueset==null || !valueset.hasExpansion()) 934 return null; 935 return findCodeInExpansion(code, valueset.getExpansion().getContains()); 936 } 937 938 private ValidationResult findCodeInExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains) { 939 for (ValueSetExpansionContainsComponent containsComponent: contains) { 940 opContext.deadCheck("findCodeInExpansion"); 941 if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) { 942 ConceptDefinitionComponent ccd = new ConceptDefinitionComponent(); 943 ccd.setCode(containsComponent.getCode()); 944 ccd.setDisplay(containsComponent.getDisplay()); 945 ValidationResult res = new ValidationResult(code.getSystem(), code.hasVersion() ? code.getVersion() : containsComponent.getVersion(), ccd, getPreferredDisplay(ccd, null)); 946 return res; 947 } 948 if (containsComponent.hasContains()) { 949 ValidationResult res = findCodeInExpansion(code, containsComponent.getContains()); 950 if (res != null) { 951 return res; 952 } 953 } 954 } 955 return null; 956 } 957 958 private boolean checkExpansion(Coding code, VersionInfo vi) { 959 if (valueset==null || !valueset.hasExpansion()) { 960 return false; 961 } 962 return checkExpansion(code, valueset.getExpansion().getContains(), vi); 963 } 964 965 private boolean checkExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains, VersionInfo vi) { 966 for (ValueSetExpansionContainsComponent containsComponent: contains) { 967 opContext.deadCheck("checkExpansion: "+code.toString()); 968 if (containsComponent.hasSystem() && containsComponent.hasCode() && containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) { 969 vi.setExpansionVersion(containsComponent.getVersion()); 970 return true; 971 } 972 if (containsComponent.hasContains() && checkExpansion(code, containsComponent.getContains(), vi)) { 973 return true; 974 } 975 } 976 return false; 977 } 978 979 private ValidationResult validateCode(String path, Coding code, CodeSystem cs, CodeableConcept vcc, ValidationProcessInfo info) { 980 if (code.getCode() == null) { 981 String msgid = cs.getVersion() == null ? I18nConstants.NO_CODE_PROVIDED : I18nConstants.NO_CODE_PROVIDED_VERSION; 982 String msg = context.formatMessage(msgid, cs.getUrl(), cs.getVersion()); 983 return new ValidationResult(IssueSeverity.WARNING, msg, makeIssue(IssueSeverity.WARNING, IssueType.VALUE, path + ".code", msg, OpIssueCode.InvalidData, null, msgid)); 984 } 985 ConceptDefinitionComponent cc = cs.hasUserData(UserDataNames.tx_cs_special) ? ((SpecialCodeSystem) cs.getUserData(UserDataNames.tx_cs_special)).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode(), cs.getCaseSensitive(), allAltCodes); 986 if (cc == null) { 987 cc = findSpecialConcept(code, cs); 988 } 989 if (cc == null) { 990 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 991 String msg = context.formatMessage(I18nConstants.UNKNOWN_CODE_IN_FRAGMENT, code.getCode(), cs.getUrl(), cs.getVersion()); 992 return new ValidationResult(IssueSeverity.WARNING, msg, makeIssue(IssueSeverity.WARNING, IssueType.CODEINVALID, path + ".code", msg, OpIssueCode.InvalidCode, null, I18nConstants.UNKNOWN_CODE_IN_FRAGMENT)); 993 } else { 994 String msg = context.formatMessage(I18nConstants.UNKNOWN_CODE_IN_VERSION, code.getCode(), cs.getUrl(), cs.getVersion()); 995 return new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path + ".code", msg, OpIssueCode.InvalidCode, null, I18nConstants.UNKNOWN_CODE_IN_VERSION)); 996 } 997 } else { 998 if (!cc.getCode().equals(code.getCode())) { 999 String msg = context.formatMessage(I18nConstants.CODE_CASE_DIFFERENCE, code.getCode(), cc.getCode(), cs.getVersionedUrl()); 1000 info.addIssue(makeIssue(IssueSeverity.INFORMATION, IssueType.BUSINESSRULE, path + ".code", msg, OpIssueCode.CodeRule, null, I18nConstants.CODE_CASE_DIFFERENCE)); 1001 } 1002 } 1003 Coding vc = new Coding().setCode(cc.getCode()).setSystem(cs.getUrl()).setVersion(cs.getVersion()).setDisplay(getPreferredDisplay(cc, cs)); 1004 if (vcc != null) { 1005 vcc.addCoding(vc); 1006 } 1007 1008 boolean inactive = (CodeSystemUtilities.isInactive(cs, cc)); 1009 String status = inactive ? (CodeSystemUtilities.getStatus(cs, cc)) : null; 1010 1011 String statusMessage = null; 1012 if (inactive) { 1013 statusMessage = context.formatMessage(I18nConstants.INACTIVE_CONCEPT_FOUND, status == null ? "inactive" : status, cc.getCode()); 1014 info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.BUSINESSRULE, path, statusMessage, OpIssueCode.CodeComment, null, I18nConstants.INACTIVE_CONCEPT_FOUND)); 1015 } 1016 boolean isDefaultLang = false; 1017 boolean ws = false; 1018 if (code.getDisplay() == null) { 1019 if (statusMessage == null) { 1020 return new ValidationResult(code.getSystem(), cs.getVersion(), cc, vc.getDisplay()).setStatus(inactive, status); 1021 } else { 1022 return new ValidationResult(IssueSeverity.WARNING, statusMessage, code.getSystem(), cs.getVersion(), cc, vc.getDisplay(), null).setStatus(inactive, status); 1023 } 1024 } 1025 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", ", " or "); 1026 if (cc.hasDisplay() && isOkLanguage(cs.getLanguage())) { 1027 b.append("'"+cc.getDisplay()+"'"+(cs.hasLanguage() ? " ("+cs.getLanguage()+")" : "")); 1028 if (code.getDisplay().equalsIgnoreCase(cc.getDisplay())) { 1029 return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); 1030 } else if (Utilities.normalize(code.getDisplay()).equals(Utilities.normalize(cc.getDisplay()))) { 1031 ws = true; 1032 } 1033 } else if (cc.hasDisplay() && code.getDisplay().equalsIgnoreCase(cc.getDisplay())) { 1034 isDefaultLang = true; 1035 } 1036 1037 for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) { 1038 opContext.deadCheck("validateCode1 "+ds.toString()); 1039 if (isOkLanguage(ds.getLanguage())) { 1040 b.append("'"+ds.getValue()+"' ("+ds.getLanguage()+")"); 1041 if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { 1042 if (ds.hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status")) { 1043 String dstatus = ds.getExtensionString("http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status"); 1044 List<String> ok = new ArrayList<>(); 1045 ok.add(cc.getDisplay()); 1046 for (ConceptDefinitionDesignationComponent ds1 : cc.getDesignation()) { 1047 if (isOkLanguage(ds.getLanguage()) && !ds.hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status")) { 1048 ok.add(ds.getValue()); 1049 } 1050 } 1051 Collections.sort(ok); 1052 String msg = context.formatMessagePlural(ok.size(), I18nConstants.INACTIVE_DISPLAY_FOUND, code.getDisplay(), cc.getCode(), CommaSeparatedStringBuilder.join(", ", ok), dstatus); 1053 info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".display", msg, OpIssueCode.DisplayComment, null, I18nConstants.INACTIVE_DISPLAY_FOUND)); 1054 } 1055 return new ValidationResult(code.getSystem(),cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); 1056 } 1057 if (Utilities.normalize(code.getDisplay()).equalsIgnoreCase(Utilities.normalize(ds.getValue()))) { 1058 ws = true; 1059 } 1060 } 1061 } 1062 // also check to see if the value set has another display 1063 if (options.isUseValueSetDisplays()) { 1064 ConceptReferencePair vs = findValueSetRef(code.getSystem(), code.getCode()); 1065 if (vs != null && (vs.getCc().hasDisplay() ||vs.getCc().hasDesignation())) { 1066 if (vs.getCc().hasDisplay() && isOkLanguage(vs.getValueset().getLanguage())) { 1067 b.append("'"+vs.getCc().getDisplay()+"'"); 1068 if (code.getDisplay().equalsIgnoreCase(vs.getCc().getDisplay())) { 1069 return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); 1070 } 1071 } 1072 for (ConceptReferenceDesignationComponent ds : vs.getCc().getDesignation()) { 1073 opContext.deadCheck("validateCode2 "+ds.toString()); 1074 if (isOkLanguage(ds.getLanguage())) { 1075 b.append("'"+ds.getValue()+"'"); 1076 if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { 1077 return new ValidationResult(code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs)).setStatus(inactive, status); 1078 } 1079 } 1080 } 1081 } 1082 } 1083 if (b.count() > 0) { 1084 String msgId = ws ? I18nConstants.DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF : I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF; 1085 String msg = context.formatMessagePlural(b.count(), msgId, code.getSystem(), code.getCode(), b.toString(), code.getDisplay(), options.langSummary()); 1086 return new ValidationResult(dispWarningStatus(), msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(dispWarning(), IssueType.INVALID, path+".display", msg, OpIssueCode.Display, null, msgId)).setStatus(inactive, status); 1087 } else if (isDefaultLang) { 1088 // we didn't find any valid displays because there aren't any, so the default language is acceptable, but we'll still add a hint about that 1089 boolean none = options.getLanguages().getLangs().size() == 1 && !hasLanguage(cs, options.getLanguages().getLangs().get(0)); 1090 String msgid = none ? I18nConstants.NO_VALID_DISPLAY_FOUND_LANG_NONE : I18nConstants.NO_VALID_DISPLAY_FOUND_LANG_SOME; 1091 String msg = context.formatMessagePlural(options.getLanguages().getLangs().size(), msgid, code.getSystem(), code.getCode(), code.getDisplay(), options.langSummary(), code.getDisplay()); 1092 String n = null; 1093 return new ValidationResult(IssueSeverity.INFORMATION, n, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(IssueSeverity.INFORMATION, IssueType.INVALID, path+".display", msg, OpIssueCode.DisplayComment, null, msgid)).setStatus(inactive, status); 1094 } else if (!code.getDisplay().equals(vc.getDisplay())) { 1095 String msg = context.formatMessage(I18nConstants.NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR, code.getDisplay(), code.getSystem(), code.getCode(), options.langSummary(), vc.getDisplay()); 1096 return new ValidationResult(IssueSeverity.ERROR, msg, code.getSystem(), cs.getVersion(), cc, cc.getDisplay(), makeIssue(dispWarning(), IssueType.INVALID, path+".display", msg, OpIssueCode.Display, null, I18nConstants.NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR)).setStatus(inactive, status).setErrorIsDisplayIssue(true); 1097 } else { 1098 String msg = context.formatMessagePlural(options.getLanguages().getLangs().size(), I18nConstants.NO_VALID_DISPLAY_FOUND, code.getSystem(), code.getCode(), code.getDisplay(), options.langSummary()); 1099 return new ValidationResult(IssueSeverity.WARNING, msg, code.getSystem(), cs.getVersion(), cc, cc.getDisplay(), makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".display", msg, OpIssueCode.Display, null, I18nConstants.NO_VALID_DISPLAY_FOUND)).setStatus(inactive, status); 1100 } 1101 } 1102 1103 private boolean hasLanguage(CodeSystem cs, LanguagePreference languagePreference) { 1104 String lang = languagePreference.getLang(); 1105 if (lang == null) { 1106 return false; 1107 } 1108 for (ConceptDefinitionComponent cc : cs.getConcept()) { 1109 boolean hl = hasLanguage(cs, cc, lang); 1110 if (hl) { 1111 return true; 1112 } 1113 } 1114 return false; 1115 } 1116 1117 private boolean hasLanguage(CodeSystem cs, ConceptDefinitionComponent cc, String lang) { 1118 if (lang.equals(cs.getLanguage()) && cc.hasDisplay()) { 1119 return true; 1120 } 1121 for (ConceptDefinitionDesignationComponent d : cc.getDesignation()) { 1122 if (lang.equals(d.getLanguage())) { 1123 return true; 1124 } 1125 } 1126 for (ConceptDefinitionComponent cc1 : cc.getConcept()) { 1127 boolean hl = hasLanguage(cs, cc1, lang); 1128 if (hl) { 1129 return true; 1130 } 1131 } 1132 return false; 1133 } 1134 1135 private ConceptDefinitionComponent findSpecialConcept(Coding c, CodeSystem cs) { 1136 // handling weird special cases in v2 code systems 1137 if ("http://terminology.hl7.org/CodeSystem/v2-0203".equals(cs.getUrl())) { 1138 String code = c.getCode(); 1139 if (code != null && code.startsWith("NN") && code.length() > 3) { 1140 ConceptDefinitionComponent cd = findCountryCode(code.substring(2)); 1141 if (cd != null) { 1142 return new ConceptDefinitionComponent(code).setDisplay("National Identifier for "+cd.getDisplay()); 1143 } 1144 } 1145 } 1146// 0396: HL7nnnn, IBTnnnn, ISOnnnn, X12Dennnn, 99zzz 1147// 0335: PRNxxx 1148 return null; 1149 } 1150 1151 1152 private ConceptDefinitionComponent findCountryCode(String code) { 1153 ValidationResult vr = context.validateCode(new ValidationOptions(FhirPublication.R5), "urn:iso:std:iso:3166", null, code, null); 1154 return vr == null || !vr.isOk() ? null : new ConceptDefinitionComponent(code).setDisplay(vr.getDisplay()).setDefinition(vr.getDefinition()); 1155 } 1156 1157 private IssueSeverity dispWarning() { 1158 return options.isDisplayWarningMode() ? IssueSeverity.WARNING : IssueSeverity.ERROR; 1159 } 1160 1161 private IssueSeverity dispWarningStatus() { 1162 return options.isDisplayWarningMode() ? IssueSeverity.WARNING : IssueSeverity.INFORMATION; // information -> error later 1163 } 1164 1165 private boolean isOkLanguage(String language) { 1166 if (!options.hasLanguages()) { 1167 return true; 1168 } 1169 if (LanguageUtils.langsMatch(options.getLanguages(), language)) { 1170 return true; 1171 } 1172 if (language == null && (options.langSummary().contains("en") || options.langSummary().contains("en-US") || options.isEnglishOk())) { 1173 return true; 1174 } 1175 return false; 1176 } 1177 1178 private ConceptReferencePair findValueSetRef(String system, String code) { 1179 if (valueset == null) 1180 return null; 1181 // if it has an expansion 1182 for (ValueSetExpansionContainsComponent exp : valueset.getExpansion().getContains()) { 1183 opContext.deadCheck("findValueSetRef "+exp.toString()); 1184 if (system.equals(exp.getSystem()) && code.equals(exp.getCode())) { 1185 ConceptReferenceComponent cc = new ConceptReferenceComponent(); 1186 cc.setDisplay(exp.getDisplay()); 1187 cc.setDesignation(exp.getDesignation()); 1188 return new ConceptReferencePair(valueset, cc); 1189 } 1190 } 1191 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 1192 if (system.equals(inc.getSystem())) { 1193 for (ConceptReferenceComponent cc : inc.getConcept()) { 1194 if (cc.getCode().equals(code)) { 1195 return new ConceptReferencePair(valueset, cc); 1196 } 1197 } 1198 } 1199 for (CanonicalType url : inc.getValueSet()) { 1200 ConceptReferencePair cc = getVs(getCu().pinValueSet(url.asStringValue(), expansionParameters), null).findValueSetRef(system, code); 1201 if (cc != null) { 1202 return cc; 1203 } 1204 } 1205 } 1206 return null; 1207 } 1208 1209 /* 1210 * Check that all system values within an expansion correspond to the specified system value 1211 */ 1212 private boolean checkSystem(List<ValueSetExpansionContainsComponent> containsList, String system) { 1213 for (ValueSetExpansionContainsComponent contains : containsList) { 1214 if (!contains.getSystem().equals(system) || (contains.hasContains() && !checkSystem(contains.getContains(), system))) { 1215 return false; 1216 } 1217 } 1218 return true; 1219 } 1220 1221 private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, boolean caseSensitive, AlternateCodesProcessingRules altCodeRules) { 1222 opContext.deadCheck("findCodeInConcept: "+code.toString()+", "+concept.toString()); 1223 if (code.equals(concept.getCode())) { 1224 return concept; 1225 } 1226 ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code, caseSensitive, altCodeRules); 1227 if (cc != null) { 1228 return cc; 1229 } 1230 if (concept.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 1231 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) concept.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 1232 for (ConceptDefinitionComponent c : children) { 1233 cc = findCodeInConcept(c, code, caseSensitive, altCodeRules); 1234 if (cc != null) { 1235 return cc; 1236 } 1237 } 1238 } 1239 return null; 1240 } 1241 1242 private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code, boolean caseSensitive, AlternateCodesProcessingRules altCodeRules) { 1243 if (code == null) { 1244 return null; 1245 } 1246 for (ConceptDefinitionComponent cc : concept) { 1247 if (code.equals(cc.getCode()) || (!caseSensitive && (code.equalsIgnoreCase(cc.getCode())))) { 1248 return cc; 1249 } 1250 if (Utilities.existsInList(code, alternateCodes(cc, altCodeRules))) { 1251 return cc; 1252 } 1253 ConceptDefinitionComponent c = findCodeInConcept(cc, code, caseSensitive, altCodeRules); 1254 if (c != null) { 1255 return c; 1256 } 1257 } 1258 return null; 1259 } 1260 1261 1262 private List<String> alternateCodes(ConceptDefinitionComponent focus, AlternateCodesProcessingRules altCodeRules) { 1263 List<String> codes = new ArrayList<>(); 1264 for (ConceptPropertyComponent p : focus.getProperty()) { 1265 if ("alternateCode".equals(p.getCode()) && (altCodeRules.passes(p.getExtension())) && p.getValue().isPrimitive()) { 1266 codes.add(p.getValue().primitiveValue()); 1267 } 1268 } 1269 return codes; 1270 } 1271 1272 1273 private String systemForCodeInValueSet(String code, List<StringWithCodes> problems) { 1274 Set<String> sys = new HashSet<>(); 1275 if (!scanForCodeInValueSet(code, sys, problems)) { 1276 return null; 1277 } 1278 if (sys.size() == 0) { 1279 problems.add(new StringWithCodes(OpIssueCode.InferFailed, context.formatMessage(I18nConstants.UNABLE_TO_INFER_CODESYSTEM, code, valueset.getVersionedUrl()), I18nConstants.UNABLE_TO_INFER_CODESYSTEM)); 1280 return null; 1281 } else if (sys.size() > 1) { 1282 problems.add(new StringWithCodes(OpIssueCode.InferFailed, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_MULTIPLE_MATCHES, code, valueset.getVersionedUrl(), sys.toString()), I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_MULTIPLE_MATCHES)); 1283 return null; 1284 } else { 1285 return sys.iterator().next(); 1286 } 1287 } 1288 1289 private boolean scanForCodeInValueSet(String code, Set<String> sys, List<StringWithCodes> problems) { 1290 if (valueset.hasCompose()) { 1291 // ignore excludes - they can't make any difference 1292 if (!valueset.getCompose().hasInclude() && !valueset.getExpansion().hasContains()) { 1293 problems.add(new StringWithCodes(OpIssueCode.InferFailed, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_NO_INCLUDES_OR_EXPANSION, code, valueset.getVersionedUrl()), I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_NO_INCLUDES_OR_EXPANSION)); 1294 } 1295 1296 int i = 0; 1297 for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { 1298 opContext.deadCheck("scanForCodeInValueSet: "+code.toString()); 1299 if (scanForCodeInValueSetInclude(code, sys, problems, i, vsi)) { 1300 return true; 1301 } 1302 i++; 1303 } 1304 } else if (valueset.hasExpansion()) { 1305 // Retrieve a list of all systems associated with this code in the expansion 1306 if (!checkSystems(valueset.getExpansion().getContains(), code, sys, problems)) { 1307 return false; 1308 } 1309 } 1310 return true; 1311 } 1312 1313 private boolean scanForCodeInValueSetInclude(String code, Set<String> sys, List<StringWithCodes> problems, int i, ConceptSetComponent vsi) { 1314 if (vsi.hasValueSet()) { 1315 for (CanonicalType u : vsi.getValueSet()) { 1316 if (!checkForCodeInValueSet(code, getCu().pinValueSet(u.getValue(), expansionParameters), sys, problems)) { 1317 return false; 1318 } 1319 } 1320 } else if (!vsi.hasSystem()) { 1321 problems.add(new StringWithCodes(OpIssueCode.InferFailed, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_NO_SYSTEM, code, valueset.getVersionedUrl(), i), I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_NO_SYSTEM)); 1322 return false; 1323 } 1324 if (vsi.hasSystem()) { 1325 if (vsi.hasFilter()) { 1326 ValueSet vsDummy = new ValueSet(); 1327 vsDummy.setUrl(UUIDUtilities.makeUuidUrn()); 1328 vsDummy.setStatus(PublicationStatus.ACTIVE); 1329 vsDummy.getCompose().addInclude(vsi); 1330 Coding c = new Coding().setCode(code).setSystem(vsi.getSystem()); 1331 ValidationResult vr = context.validateCode(options.withGuessSystem(false), c, vsDummy); 1332 if (vr.isOk()) { 1333 sys.add(vsi.getSystem()); 1334 } else { 1335 // problems.add(new StringWithCode(OpIssueCode.InferFailed, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_FILTER, code, valueset.getVersionedUrl(), i, vsi.getSystem(), filterSummary(vsi)))); 1336 return false; 1337 } 1338 } 1339 CodeSystemProvider csp = CodeSystemProvider.factory(vsi.getSystem()); 1340 if (csp != null) { 1341 Boolean ok = csp.checkCode(code); 1342 if (ok == null) { 1343 problems.add(new StringWithCodes(OpIssueCode.InferFailed, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE, code, valueset.getVersionedUrl(), vsi.getSystem()), I18nConstants.UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE)); 1344 sys.add(vsi.getSystem()); 1345 } else if (ok) { 1346 sys.add(vsi.getSystem()); 1347 } 1348 } else { 1349 CodeSystem cs = resolveCodeSystem(vsi.getSystem(), vsi.getVersion()); 1350 if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) { 1351 if (vsi.hasConcept()) { 1352 for (ConceptReferenceComponent cc : vsi.getConcept()) { 1353 boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code); 1354 if (match) { 1355 sys.add(vsi.getSystem()); 1356 } 1357 } 1358 } else { 1359 ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code, cs.getCaseSensitive(), allAltCodes); 1360 if (cc != null) { 1361 sys.add(vsi.getSystem()); 1362 } 1363 } 1364 } else if (vsi.hasConcept()) { 1365 for (ConceptReferenceComponent cc : vsi.getConcept()) { 1366 boolean match = cc.getCode().equals(code); 1367 if (match) { 1368 sys.add(vsi.getSystem()); 1369 } 1370 } 1371 } else if (!VersionUtilities.isR6Plus(context.getVersion()) && Utilities.existsInList(code, "xml", "json", "ttl") && "urn:ietf:bcp:13".equals(vsi.getSystem())) { 1372 sys.add(vsi.getSystem()); 1373 return true; 1374 } else { 1375 ValueSet vsDummy = new ValueSet(); 1376 vsDummy.setUrl(UUIDUtilities.makeUuidUrn()); 1377 vsDummy.setStatus(PublicationStatus.ACTIVE); 1378 vsDummy.getCompose().addInclude(vsi); 1379 ValidationResult vr = context.validateCode(options.withNoClient(), code, vsDummy); 1380 if (vr.isOk()) { 1381 sys.add(vsi.getSystem()); 1382 } else { 1383 // ok, we'll try to expand this one then 1384 ValueSetExpansionOutcome vse = context.expandVS(new TerminologyOperationDetails(requiredSupplements), vsi, false, false); 1385 if (vse.isOk()) { 1386 if (!checkSystems(vse.getValueset().getExpansion().getContains(), code, sys, problems)) { 1387 return false; 1388 } 1389 } else { 1390 problems.add(new StringWithCodes(OpIssueCode.NotFound, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_UNKNOWN_SYSTEM, code, valueset.getVersionedUrl(), i, vsi.getSystem(), vse.getAllErrors().toString()), I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_UNKNOWN_SYSTEM)); 1391 return false; 1392 } 1393 1394 } 1395 } 1396 } 1397 } 1398 return false; 1399 } 1400 1401 private String filterSummary(ConceptSetComponent vsi) { 1402 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1403 for (ConceptSetFilterComponent f : vsi.getFilter()) { 1404 b.append(f.getProperty()+f.getOp().toCode()+f.getValue()); 1405 } 1406 return b.toString(); 1407 } 1408 1409 private boolean checkForCodeInValueSet(String code, String uri, Set<String> sys, List<StringWithCodes> problems) { 1410 ValueSetValidator vs = getVs(uri, null); 1411 return vs.scanForCodeInValueSet(code, sys, problems); 1412 } 1413 1414 /* 1415 * Recursively go through all codes in the expansion and for any coding that matches the specified code, add the system for that coding 1416 * to the passed list. 1417 */ 1418 private boolean checkSystems(List<ValueSetExpansionContainsComponent> contains, String code, Set<String> systems, List<StringWithCodes> problems) { 1419 for (ValueSetExpansionContainsComponent c: contains) { 1420 opContext.deadCheck("checkSystems "+code.toString()); 1421 if (c.getCode().equals(code)) { 1422 systems.add(c.getSystem()); 1423 } 1424 if (c.hasContains()) 1425 checkSystems(c.getContains(), code, systems, problems); 1426 } 1427 return true; 1428 } 1429 1430 public Boolean codeInValueSet(String path, String system, String version, String code, ValidationProcessInfo info) throws FHIRException { 1431 if (valueset == null) { 1432 return null; 1433 } 1434 opContext.deadCheck("codeInValueSet: "+system+"#"+code); 1435 checkCanonical(info.getIssues(), path, valueset, valueset); 1436 Boolean result = false; 1437 VersionInfo vi = new VersionInfo(this); 1438 String vspath = "ValueSet['"+valueset.getVersionedUrl()+"].compose"; 1439 1440 if (valueset.hasExpansion()) { 1441 return checkExpansion(new Coding(system, code, null), vi); 1442 } else if (valueset.hasCompose()) { 1443 int i = 0; 1444 int c = 0; 1445 for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { 1446 Boolean ok = inComponent(path, vsi, i, system, version, code, valueset.getCompose().getInclude().size() == 1, info, vspath+".include["+c+"]"); 1447 i++; 1448 c++; 1449 if (ok == null && result != null && result == false) { 1450 result = null; 1451 } else if (ok != null && ok) { 1452 result = true; 1453 break; 1454 } 1455 } 1456 i = valueset.getCompose().getInclude().size(); 1457 c = 0; 1458 for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) { 1459 Boolean nok = inComponent(path, vsi, i, system, version, code, valueset.getCompose().getInclude().size() == 1, info, vspath+".exclude["+c+"]"); 1460 i++; 1461 c++; 1462 if (nok == null && result != null && result == false) { 1463 result = null; 1464 } else if (nok != null && nok) { 1465 result = false; 1466 } 1467 } 1468 } 1469 1470 return result; 1471 } 1472 1473 private Boolean inComponent(String path, ConceptSetComponent vsi, int vsiIndex, String system, String version, String code, boolean only, ValidationProcessInfo info, String vspath) throws FHIRException { 1474 opContext.deadCheck("inComponent "+vsiIndex); 1475 boolean ok = true; 1476 1477 if (vsi.hasValueSet()) { 1478 if (isValueSetUnionImports()) { 1479 ok = false; 1480 for (UriType uri : vsi.getValueSet()) { 1481 if (inImport(path, getCu().pinValueSet(uri.getValue(), expansionParameters), system, version, code, info)) { 1482 return true; 1483 } 1484 } 1485 } else { 1486 Boolean bok = inImport(path, getCu().pinValueSet(vsi.getValueSet().get(0).getValue(), expansionParameters), system, version, code, info); 1487 if (bok == null) { 1488 return bok; 1489 } 1490 ok = bok; 1491 for (int i = 1; i < vsi.getValueSet().size(); i++) { 1492 UriType uri = vsi.getValueSet().get(i); 1493 ok = ok && inImport(path, getCu().pinValueSet(uri.getValue(), expansionParameters), system, version, code, info); 1494 } 1495 } 1496 } 1497 1498 if (!vsi.hasSystem() || !ok) { 1499 return ok; 1500 } 1501 1502 if (only && system == null) { 1503 // whether we know the system or not, we'll accept the stated codes at face value 1504 for (ConceptReferenceComponent cc : vsi.getConcept()) { 1505 if (cc.getCode().equals(code)) { 1506 return true; 1507 } 1508 } 1509 } 1510 1511 if (system == null || !system.equals(vsi.getSystem())) 1512 return false; 1513 // ok, we need the code system 1514 CodeSystem cs = resolveCodeSystem(system, version); 1515 if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)) { 1516 if (throwToServer) { 1517 // make up a transient value set with 1518 ValueSet vs = new ValueSet(); 1519 vs.setStatus(PublicationStatus.ACTIVE); 1520 vs.setUrl(valueset.getUrl()+"--"+vsiIndex); 1521 vs.setVersion(valueset.getVersion()); 1522 vs.getCompose().addInclude(vsi); 1523 opContext.deadCheck("hit server "+vs.getVersionedUrl()); 1524 ValidationResult res = context.validateCode(options.withNoClient(), new Coding(system, code, null), vs); 1525 if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { 1526 if (info != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 1527 // server didn't know the code system either - we'll take it face value 1528 if (!info.hasNotFound(system)) { 1529 String msg = context.formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, system); 1530 info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.UNKNOWN, path, msg, OpIssueCode.NotFound, null, I18nConstants.UNKNOWN_CODESYSTEM)); 1531 for (ConceptReferenceComponent cc : vsi.getConcept()) { 1532 if (cc.getCode().equals(code)) { 1533 opContext.deadCheck("server true"); 1534 return true; 1535 } 1536 } 1537 } 1538 info.setErr(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 1539 opContext.deadCheck("server codesystem unsupported"); 1540 return null; 1541 } 1542 opContext.deadCheck("server not found"); 1543 return false; 1544 } 1545 if (res.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) { 1546 opContext.deadCheck("server no server"); 1547 throw new NoTerminologyServiceException(); 1548 } 1549 return res.isOk(); 1550 } else { 1551 info.setErr(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 1552 if (unknownSystems != null) { 1553 if (version == null) { 1554 unknownSystems.add(system); 1555 } else { 1556 unknownSystems.add(system+"|"+version); 1557 } 1558 } 1559 return null; 1560 } 1561 } else { 1562 checkCanonical(info.getIssues(), path, cs, valueset); 1563 if ((valueset.getCompose().hasInactive() && !valueset.getCompose().getInactive()) || options.isActiveOnly()) { 1564 if (CodeSystemUtilities.isInactive(cs, code)) { 1565 info.addIssue(makeIssue(IssueSeverity.ERROR, IssueType.BUSINESSRULE, path+".code", context.formatMessage(I18nConstants.STATUS_CODE_WARNING_CODE, "not active", code), OpIssueCode.CodeRule, null, I18nConstants.STATUS_CODE_WARNING_CODE)); 1566 return false; 1567 } 1568 } 1569 1570 if (vsi.hasFilter()) { 1571 ok = true; 1572 int i = 0; 1573 for (ConceptSetFilterComponent f : vsi.getFilter()) { 1574 if (!codeInFilter(cs, vspath+".filter["+i+"]", system, f, code)) { 1575 return false; 1576 } 1577 i++; 1578 } 1579 } 1580 1581 List<ConceptDefinitionComponent> list = cs.getConcept(); 1582 ok = validateCodeInConceptList(code, cs, list, allAltCodes); 1583 if (ok && vsi.hasConcept()) { 1584 for (ConceptReferenceComponent cc : vsi.getConcept()) { 1585 if (cc.getCode().equals(code)) { 1586 String sstatus = cc.getExtensionString(ExtensionDefinitions.EXT_STANDARDS_STATUS); 1587 if (Utilities.existsInList(sstatus, "withdrawn", "deprecated")) { 1588 String msg = context.formatMessage(I18nConstants.CONCEPT_DEPRECATED_IN_VALUESET, cs.getUrl(), cc.getCode(), sstatus, valueset.getVersionedUrl()); 1589 info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.BUSINESSRULE, path+".code", msg, OpIssueCode.CodeComment, null, I18nConstants.CONCEPT_DEPRECATED_IN_VALUESET)); 1590 } else if (cc.hasExtension(ExtensionDefinitions.EXT_VALUESET_DEPRECATED)) { 1591 String msg = context.formatMessage(I18nConstants.CONCEPT_DEPRECATED_IN_VALUESET, cs.getUrl(), cc.getCode(), "deprecated", valueset.getVersionedUrl()); 1592 info.addIssue(makeIssue(IssueSeverity.WARNING, IssueType.BUSINESSRULE, path+".code", msg, OpIssueCode.CodeComment, null, I18nConstants.CONCEPT_DEPRECATED_IN_VALUESET)); 1593 } 1594 return true; 1595 } 1596 } 1597 return false; 1598 } else { 1599 // recheck that this is a valid alternate code 1600 ok = validateCodeInConceptList(code, cs, list, altCodeParams); 1601 return ok; 1602 } 1603 } 1604 } 1605 1606 protected boolean isValueSetUnionImports() { 1607 PackageInformation p = (PackageInformation) valueset.getSourcePackage(); 1608 if (p != null) { 1609 return p.getDate().before(new GregorianCalendar(2022, Calendar.MARCH, 31).getTime()); 1610 } else { 1611 return false; 1612 } 1613 } 1614 1615 private boolean codeInFilter(CodeSystem cs, String path, String system, ConceptSetFilterComponent f, String code) throws FHIRException { 1616 String v = f.getValue(); 1617 if (v == null) { 1618 List<OperationOutcomeIssueComponent> issues = new ArrayList<>(); 1619 issues.addAll(makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path+".value", context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE, 1620 cs.getUrl(), f.getProperty(), f.getOp().toCode()), OpIssueCode.VSProcessing, null, I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE)); 1621 throw new VSCheckerException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE, cs.getUrl(), f.getProperty(), f.getOp().toCode()), issues, TerminologyServiceErrorClass.INTERNAL_ERROR); 1622 1623 } 1624 if ("concept".equals(f.getProperty())) 1625 return codeInConceptFilter(cs, f, code); 1626 else if ("code".equals(f.getProperty()) && f.getOp() == FilterOperator.REGEX) 1627 return codeInRegexFilter(cs, f, code); 1628 else if (CodeSystemUtilities.isDefinedProperty(cs, f.getProperty())) { 1629 return codeInPropertyFilter(cs, f, code); 1630 } else if (isKnownProperty(f.getProperty())) { 1631 return codeInKnownPropertyFilter(cs, f, code); 1632 } else { 1633 log.error("todo: handle filters with property = "+f.getProperty()+" "+f.getOp().toCode()); 1634 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__FILTER_WITH_PROPERTY__, cs.getUrl(), f.getProperty(), f.getOp().toCode())); 1635 } 1636 } 1637 1638 private boolean isKnownProperty(String code) { 1639 return Utilities.existsInList(code, "notSelectable"); 1640 } 1641 1642 private boolean codeInPropertyFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) { 1643 switch (f.getOp()) { 1644 case EQUAL: 1645 if (f.getValue() == null) { 1646 return false; 1647 } 1648 DataType d = CodeSystemUtilities.getProperty(cs, code, f.getProperty()); 1649 return d != null && f.getValue().equals(d.primitiveValue()); 1650 case EXISTS: 1651 return CodeSystemUtilities.getProperty(cs, code, f.getProperty()) != null; 1652 case REGEX: 1653 if (f.getValue() == null) { 1654 return false; 1655 } 1656 d = CodeSystemUtilities.getProperty(cs, code, f.getProperty()); 1657 return d != null && d.primitiveValue() != null && d.primitiveValue().matches(f.getValue()); 1658 case IN: 1659 if (f.getValue() == null) { 1660 return false; 1661 } 1662 String[] values = f.getValue().split("\\,"); 1663 d = CodeSystemUtilities.getProperty(cs, code, f.getProperty()); 1664 if (d != null) { 1665 String v = d.primitiveValue(); 1666 for (String value : values) { 1667 if (v != null && v.equals(value.trim())) { 1668 return true; 1669 } 1670 } 1671 } 1672 return false; 1673 case NOTIN: 1674 if (f.getValue() == null) { 1675 return true; 1676 } 1677 values = f.getValue().split("\\,"); 1678 d = CodeSystemUtilities.getProperty(cs, code, f.getProperty()); 1679 if (d != null) { 1680 String v = d.primitiveValue(); 1681 for (String value : values) { 1682 if (v != null && v.equals(value.trim())) { 1683 return false; 1684 } 1685 } 1686 } 1687 return true; 1688 default: 1689 log.error("todo: handle property filters with op = "+f.getOp()); 1690 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__PROPERTY_FILTER_WITH_OP__, cs.getUrl(), f.getOp())); 1691 } 1692 } 1693 1694 private boolean codeInKnownPropertyFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) { 1695 1696 switch (f.getOp()) { 1697 case EQUAL: 1698 if (f.getValue() == null) { 1699 return false; 1700 } 1701 DataType d = CodeSystemUtilities.getProperty(cs, code, f.getProperty()); 1702 return d != null && f.getValue().equals(d.primitiveValue()); 1703 case EXISTS: 1704 return CodeSystemUtilities.getProperty(cs, code, f.getProperty()) != null; 1705 case REGEX: 1706 if (f.getValue() == null) { 1707 return false; 1708 } 1709 d = CodeSystemUtilities.getProperty(cs, code, f.getProperty()); 1710 return d != null && d.primitiveValue() != null && d.primitiveValue().matches(f.getValue()); 1711 default: 1712 log.error("todo: handle known property filters with op = "+f.getOp()); 1713 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__PROPERTY_FILTER_WITH_OP__, cs.getUrl(), f.getOp())); 1714 } 1715 } 1716 1717 private boolean codeInRegexFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) { 1718 return code.matches(f.getValue()); 1719 } 1720 1721 private boolean codeInConceptFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) throws FHIRException { 1722 switch (f.getOp()) { 1723 case ISA: return codeInConceptIsAFilter(cs, f, code, false); 1724 case ISNOTA: return !codeInConceptIsAFilter(cs, f, code, false); 1725 case DESCENDENTOF: return codeInConceptIsAFilter(cs, f, code, true); 1726 default: 1727 log.error("todo: handle concept filters with op = "+f.getOp()); 1728 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__CONCEPT_FILTER_WITH_OP__, cs.getUrl(), f.getOp())); 1729 } 1730 } 1731 1732 private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code, boolean excludeRoot) { 1733 if (!excludeRoot && code.equals(f.getValue())) { 1734 return true; 1735 } 1736 ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue(), cs.getCaseSensitive(), altCodeParams); 1737 if (cc == null) { 1738 return false; 1739 } 1740 ConceptDefinitionComponent cc2 = findCodeInConcept(cc, code, cs.getCaseSensitive(), altCodeParams); 1741 return cc2 != null && cc2 != cc; 1742 } 1743 1744 public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list, AlternateCodesProcessingRules altCodeRules) { 1745 opContext.deadCheck("validateCodeInConceptList"); 1746 if (def.hasUserData(UserDataNames.tx_cs_special)) { 1747 return ((SpecialCodeSystem) def.getUserData(UserDataNames.tx_cs_special)).findConcept(new Coding().setCode(code)) != null; 1748 } else if (def.getCaseSensitive()) { 1749 for (ConceptDefinitionComponent cc : list) { 1750 if (cc.getCode().equals(code)) { 1751 return true; 1752 } 1753 if (Utilities.existsInList(code, alternateCodes(cc, altCodeRules))) { 1754 return true; 1755 } 1756 if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept(), altCodeRules)) { 1757 return true; 1758 } 1759 } 1760 } else { 1761 for (ConceptDefinitionComponent cc : list) { 1762 if (cc.getCode().equalsIgnoreCase(code)) { 1763 return true; 1764 } 1765 if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept(), altCodeRules)) { 1766 return true; 1767 } 1768 } 1769 } 1770 return false; 1771 } 1772 1773 private ValueSetValidator getVs(String url, ValidationProcessInfo info) { 1774 if (inner.containsKey(url)) { 1775 return inner.get(url); 1776 } 1777 ValueSet vs = context.findTxResource(ValueSet.class, url, valueset); 1778 if (vs == null && info != null) { 1779 unknownValueSets.add(url); 1780 info.addIssue(makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, null, context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, url), OpIssueCode.NotFound, null, I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_)); 1781 } 1782 ValueSetValidator vsc = new ValueSetValidator(context, opContext.copy(), options, vs, localContext, expansionParameters, tcm, registry); 1783 vsc.setThrowToServer(throwToServer); 1784 inner.put(url, vsc); 1785 return vsc; 1786 } 1787 1788 private Boolean inImport(String path, String uri, String system, String version, String code, ValidationProcessInfo info) throws FHIRException { 1789 ValueSetValidator vs = getVs(uri, info); 1790 if (vs == null) { 1791 return false; 1792 } else { 1793 Boolean ok = vs.codeInValueSet(path, system, version, code, info); 1794 return ok; 1795 } 1796 } 1797 1798 1799 private String getPreferredDisplay(ConceptReferenceComponent cc) { 1800 if (!options.hasLanguages()) { 1801 return cc.getDisplay(); 1802 } 1803 if (LanguageUtils.langsMatch(options.getLanguages(), valueset.getLanguage())) { 1804 return cc.getDisplay(); 1805 } 1806 // if there's no language, we default to accepting the displays as (US) English 1807 if (valueset.getLanguage() == null && (options.langSummary().contains("en") || options.langSummary().contains("en-US"))) { 1808 return cc.getDisplay(); 1809 } 1810 for (ConceptReferenceDesignationComponent d : cc.getDesignation()) { 1811 if (!d.hasUse() && LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) { 1812 return d.getValue(); 1813 } 1814 } 1815 for (ConceptReferenceDesignationComponent d : cc.getDesignation()) { 1816 if (LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) { 1817 return d.getValue(); 1818 } 1819 } 1820 return cc.getDisplay(); 1821 } 1822 1823 1824 private String getPreferredDisplay(ConceptDefinitionComponent cc, CodeSystem cs) { 1825 if (!options.hasLanguages()) { 1826 return cc.getDisplay(); 1827 } 1828 if (cs != null && LanguageUtils.langsMatch(options.getLanguages(), cs.getLanguage())) { 1829 return cc.getDisplay(); 1830 } 1831 // if there's no language, we default to accepting the displays as (US) English 1832 if ((cs == null || cs.getLanguage() == null) && (options.langSummary().contains("en") || options.langSummary().contains("en-US"))) { 1833 return cc.getDisplay(); 1834 } 1835 for (ConceptDefinitionDesignationComponent d : cc.getDesignation()) { 1836 if (!d.hasUse() && LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) { 1837 return d.getValue(); 1838 } 1839 } 1840 for (ConceptDefinitionDesignationComponent d : cc.getDesignation()) { 1841 if (LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) { 1842 return d.getValue(); 1843 } 1844 } 1845 return cc.getDisplay(); 1846 } 1847 1848}