001package org.hl7.fhir.r5.terminologies; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.util.ArrayList; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038 039import org.hl7.fhir.exceptions.FHIRException; 040import org.hl7.fhir.exceptions.NoTerminologyServiceException; 041import org.hl7.fhir.r5.context.IWorkerContext; 042import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 043import org.hl7.fhir.r5.model.CanonicalType; 044import org.hl7.fhir.r5.model.CodeSystem; 045import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode; 046import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 047import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 048import org.hl7.fhir.r5.model.CodeableConcept; 049import org.hl7.fhir.r5.model.Coding; 050import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 051import org.hl7.fhir.r5.model.UriType; 052import org.hl7.fhir.r5.model.ValueSet; 053import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 054import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 055import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 056import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 057import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 058import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 059import org.hl7.fhir.r5.utils.ToolingExtensions; 060import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; 061import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationContextResourceProxy; 062import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 063import org.hl7.fhir.utilities.Utilities; 064import org.hl7.fhir.utilities.i18n.I18nConstants; 065import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 066import org.hl7.fhir.utilities.validation.ValidationOptions; 067import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode; 068 069public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChecker { 070 071 private ValueSet valueset; 072 private IWorkerContext context; 073 private Map<String, ValueSetCheckerSimple> inner = new HashMap<>(); 074 private ValidationOptions options; 075 private ValidationContextCarrier localContext; 076 private List<CodeSystem> localSystems = new ArrayList<>(); 077 078 public ValueSetCheckerSimple(ValidationOptions options, ValueSet source, IWorkerContext context) { 079 this.valueset = source; 080 this.context = context; 081 this.options = options; 082 } 083 084 public ValueSetCheckerSimple(ValidationOptions options, ValueSet source, IWorkerContext context, ValidationContextCarrier ctxt) { 085 this.valueset = source; 086 this.context = context; 087 this.options = options; 088 this.localContext = ctxt; 089 analyseValueSet(); 090 } 091 092 private void analyseValueSet() { 093 if (localContext != null) { 094 if (valueset != null) { 095 for (ConceptSetComponent i : valueset.getCompose().getInclude()) { 096 analyseComponent(i); 097 } 098 for (ConceptSetComponent i : valueset.getCompose().getExclude()) { 099 analyseComponent(i); 100 } 101 } 102 } 103 } 104 105 private void analyseComponent(ConceptSetComponent i) { 106 if (i.getSystemElement().hasExtension(ToolingExtensions.EXT_VALUESET_SYSTEM)) { 107 String ref = i.getSystemElement().getExtensionString(ToolingExtensions.EXT_VALUESET_SYSTEM); 108 if (ref.startsWith("#")) { 109 String id = ref.substring(1); 110 for (ValidationContextResourceProxy t : localContext.getResources()) { 111 CodeSystem cs = (CodeSystem) t.loadContainedResource(id, CodeSystem.class); 112 if (cs != null) { 113 localSystems.add(cs); 114 } 115 } 116 } else { 117 throw new Error("Not done yet #2: "+ref); 118 } 119 } 120 } 121 122 public ValidationResult validateCode(CodeableConcept code) throws FHIRException { 123 // first, we validate the codings themselves 124 List<String> errors = new ArrayList<String>(); 125 List<String> warnings = new ArrayList<String>(); 126 if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { 127 for (Coding c : code.getCoding()) { 128 if (!c.hasSystem()) { 129 warnings.add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); 130 } 131 CodeSystem cs = resolveCodeSystem(c.getSystem()); 132 ValidationResult res = null; 133 if (cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) { 134 res = context.validateCode(options.noClient(), c, null); 135 } else { 136 res = validateCode(c, cs); 137 } 138 if (!res.isOk()) { 139 errors.add(res.getMessage()); 140 } else if (res.getMessage() != null) { 141 warnings.add(res.getMessage()); 142 } 143 } 144 } 145 Coding foundCoding = null; 146 if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { 147 Boolean result = false; 148 for (Coding c : code.getCoding()) { 149 Boolean ok = codeInValueSet(c.getSystem(), c.getCode(), warnings); 150 if (ok == null && result == false) { 151 result = null; 152 } else if (ok) { 153 result = true; 154 foundCoding = c; 155 } 156 } 157 if (result == null) { 158 warnings.add(0, context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); 159 } else if (!result) { 160 errors.add(0, context.formatMessage(I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); 161 } 162 } 163 if (errors.size() > 0) { 164 return new ValidationResult(IssueSeverity.ERROR, errors.toString()); 165 } else if (warnings.size() > 0) { 166 return new ValidationResult(IssueSeverity.WARNING, warnings.toString()); 167 } else { 168 ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); 169 cd.setDisplay(foundCoding.getDisplay()); 170 return new ValidationResult(foundCoding.getSystem(), cd); 171 } 172 } 173 174 public CodeSystem resolveCodeSystem(String system) { 175 for (CodeSystem t : localSystems) { 176 if (t.getUrl().equals(system)) { 177 return t; 178 } 179 } 180 CodeSystem cs = context.fetchCodeSystem(system); 181 if (cs == null) { 182 cs = findSpecialCodeSystem(system); 183 } 184 return cs; 185 } 186 187 public ValidationResult validateCode(Coding code) throws FHIRException { 188 String warningMessage = null; 189 // first, we validate the concept itself 190 191 ValidationResult res = null; 192 boolean inExpansion = false; 193 boolean inInclude = false; 194 String system = code.hasSystem() ? code.getSystem() : getValueSetSystemOrNull(); 195 if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { 196 if (system == null && !code.hasDisplay()) { // dealing with just a plain code (enum) 197 system = systemForCodeInValueSet(code.getCode()); 198 } 199 if (!code.hasSystem()) { 200 if (options.isGuessSystem() && system == null && Utilities.isAbsoluteUrl(code.getCode())) { 201 system = "urn:ietf:rfc:3986"; // this arises when using URIs bound to value sets 202 } 203 code.setSystem(system); 204 } 205 inExpansion = checkExpansion(code); 206 inInclude = checkInclude(code); 207 CodeSystem cs = resolveCodeSystem(system); 208 if (cs == null) { 209 warningMessage = "Unable to resolve system "+system; 210 if (!inExpansion) { 211 if (valueset != null && valueset.hasExpansion()) { 212 return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.CODESYSTEM_CS_UNK_EXPANSION, valueset.getUrl(), code.getCode().toString(), code.getSystem())); 213 } else { 214 throw new FHIRException(warningMessage); 215 } 216 } 217 } 218 if (cs != null && cs.hasSupplements()) { 219 return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getUrl())); 220 } 221 if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) { 222 warningMessage = "Resolved system "+system+", but the definition is not complete"; 223 if (!inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment 224 throw new FHIRException(warningMessage); 225 } 226 } 227 228 if (cs != null /*&& (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)*/) { 229 if (!(cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) { 230 // we can't validate that here. 231 throw new FHIRException("Unable to evaluate based on empty code system"); 232 } 233 res = validateCode(code, cs); 234 } else if (cs == null && valueset.hasExpansion() && inExpansion) { 235 // we just take the value set as face value then 236 res = new ValidationResult(system, new ConceptDefinitionComponent().setCode(code.getCode()).setDisplay(code.getDisplay())); 237 } else { 238 // well, we didn't find a code system - try the expansion? 239 // disabled waiting for discussion 240 throw new FHIRException("No try the server"); 241 } 242 } else { 243 inExpansion = checkExpansion(code); 244 inInclude = checkInclude(code); 245 } 246 247 List<String> warnings = new ArrayList<>(); 248 249 // then, if we have a value set, we check it's in the value set 250 if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { 251 if ((res==null || res.isOk())) { 252 Boolean ok = codeInValueSet(system, code.getCode(), warnings); 253 if (ok == null || !ok) { 254 if (res == null) { 255 res = new ValidationResult((IssueSeverity) null, null); 256 } 257 if (!inExpansion && !inInclude) { 258 if (warnings != null) { 259 res.setMessage("Not in value set "+valueset.getUrl()+" ("+warnings+")").setSeverity(IssueSeverity.ERROR); 260 } else { 261 res.setMessage("Not in value set "+valueset.getUrl()).setSeverity(IssueSeverity.ERROR); 262 } 263 } else if (warningMessage!=null) { 264 res = new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_, warningMessage)); 265 } else if (inExpansion) { 266 res.setMessage("Code found in expansion, however: " + res.getMessage()); 267 } else if (inInclude) { 268 res.setMessage("Code found in include, however: " + res.getMessage()); 269 } 270 } 271 } 272 } 273 return res; 274 } 275 276 private boolean checkInclude(Coding code) { 277 if (valueset == null || code.getSystem() == null || code.getCode() == null) { 278 return false; 279 } 280 for (ConceptSetComponent inc : valueset.getCompose().getExclude()) { 281 if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { 282 for (ConceptReferenceComponent cc : inc.getConcept()) { 283 if (cc.hasCode() && cc.getCode().equals(code.getCode())) { 284 return false; 285 } 286 } 287 } 288 } 289 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 290 if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { 291 for (ConceptReferenceComponent cc : inc.getConcept()) { 292 if (cc.hasCode() && cc.getCode().equals(code.getCode())) { 293 return true; 294 } 295 } 296 } 297 } 298 return false; 299 } 300 301 private CodeSystem findSpecialCodeSystem(String system) { 302 if ("urn:ietf:rfc:3986".equals(system)) { 303 CodeSystem cs = new CodeSystem(); 304 cs.setUrl(system); 305 cs.setUserData("tx.cs.special", new URICodeSystem()); 306 cs.setContent(CodeSystemContentMode.COMPLETE); 307 return cs; 308 } 309 return null; 310 } 311 312 private ValidationResult findCodeInExpansion(Coding code) { 313 if (valueset==null || !valueset.hasExpansion()) 314 return null; 315 return findCodeInExpansion(code, valueset.getExpansion().getContains()); 316 } 317 318 private ValidationResult findCodeInExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains) { 319 for (ValueSetExpansionContainsComponent containsComponent: contains) { 320 if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) { 321 ConceptDefinitionComponent ccd = new ConceptDefinitionComponent(); 322 ccd.setCode(containsComponent.getCode()); 323 ccd.setDisplay(containsComponent.getDisplay()); 324 ValidationResult res = new ValidationResult(code.getSystem(), ccd); 325 return res; 326 } 327 if (containsComponent.hasContains()) { 328 ValidationResult res = findCodeInExpansion(code, containsComponent.getContains()); 329 if (res != null) { 330 return res; 331 } 332 } 333 } 334 return null; 335 } 336 337 private boolean checkExpansion(Coding code) { 338 if (valueset==null || !valueset.hasExpansion()) { 339 return false; 340 } 341 return checkExpansion(code, valueset.getExpansion().getContains()); 342 } 343 344 private boolean checkExpansion(Coding code, List<ValueSetExpansionContainsComponent> contains) { 345 for (ValueSetExpansionContainsComponent containsComponent: contains) { 346 if (containsComponent.getSystem().equals(code.getSystem()) && containsComponent.getCode().equals(code.getCode())) { 347 return true; 348 } 349 if (containsComponent.hasContains() && checkExpansion(code, containsComponent.getContains())) { 350 return true; 351 } 352 } 353 return false; 354 } 355 356 private ValidationResult validateCode(Coding code, CodeSystem cs) { 357 ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode()); 358 if (cc == null) { 359 if (cs.getContent() == CodeSystemContentMode.FRAGMENT) { 360 return new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_FRAGMENT, gen(code), cs.getUrl())); 361 } else { 362 return new ValidationResult(IssueSeverity.ERROR, context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_, gen(code), cs.getUrl())); 363 } 364 } 365 if (code.getDisplay() == null) { 366 return new ValidationResult(code.getSystem(), cc); 367 } 368 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 369 if (cc.hasDisplay()) { 370 b.append(cc.getDisplay()); 371 if (code.getDisplay().equalsIgnoreCase(cc.getDisplay())) { 372 return new ValidationResult(code.getSystem(), cc); 373 } 374 } 375 for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) { 376 b.append(ds.getValue()); 377 if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { 378 return new ValidationResult(code.getSystem(), cc); 379 } 380 } 381 // also check to see if the value set has another display 382 ConceptReferenceComponent vs = findValueSetRef(code.getSystem(), code.getCode()); 383 if (vs != null && (vs.hasDisplay() ||vs.hasDesignation())) { 384 if (vs.hasDisplay()) { 385 b.append(vs.getDisplay()); 386 if (code.getDisplay().equalsIgnoreCase(vs.getDisplay())) { 387 return new ValidationResult(code.getSystem(), cc); 388 } 389 } 390 for (ConceptReferenceDesignationComponent ds : vs.getDesignation()) { 391 b.append(ds.getValue()); 392 if (code.getDisplay().equalsIgnoreCase(ds.getValue())) { 393 return new ValidationResult(code.getSystem(), cc); 394 } 395 } 396 } 397 return new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF_, code.getSystem(), code.getCode(), b.toString(), code.getDisplay()), code.getSystem(), cc); 398 } 399 400 private ConceptReferenceComponent findValueSetRef(String system, String code) { 401 if (valueset == null) 402 return null; 403 // if it has an expansion 404 for (ValueSetExpansionContainsComponent exp : valueset.getExpansion().getContains()) { 405 if (system.equals(exp.getSystem()) && code.equals(exp.getCode())) { 406 ConceptReferenceComponent cc = new ConceptReferenceComponent(); 407 cc.setDisplay(exp.getDisplay()); 408 cc.setDesignation(exp.getDesignation()); 409 return cc; 410 } 411 } 412 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 413 if (system.equals(inc.getSystem())) { 414 for (ConceptReferenceComponent cc : inc.getConcept()) { 415 if (cc.getCode().equals(code)) { 416 return cc; 417 } 418 } 419 } 420 for (CanonicalType url : inc.getValueSet()) { 421 ConceptReferenceComponent cc = getVs(url.asStringValue()).findValueSetRef(system, code); 422 if (cc != null) { 423 return cc; 424 } 425 } 426 } 427 return null; 428 } 429 430 private String gen(Coding code) { 431 if (code.hasSystem()) { 432 return code.getSystem()+"#"+code.getCode(); 433 } else { 434 return null; 435 } 436 } 437 438 private String getValueSetSystem() throws FHIRException { 439 if (valueset == null) { 440 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__NO_VALUE_SET)); 441 } 442 if (valueset.getCompose().getInclude().size() == 0) { 443 if (!valueset.hasExpansion() || valueset.getExpansion().getContains().size() == 0) { 444 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_NO_INCLUDES_OR_EXPANSION)); 445 } else { 446 String cs = valueset.getExpansion().getContains().get(0).getSystem(); 447 if (cs != null && checkSystem(valueset.getExpansion().getContains(), cs)) { 448 return cs; 449 } else { 450 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_EXPANSION_HAS_MULTIPLE_SYSTEMS)); 451 } 452 } 453 } 454 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 455 if (inc.hasValueSet()) { 456 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_IMPORTS)); 457 } 458 if (!inc.hasSystem()) { 459 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_NO_SYSTEM)); 460 } 461 } 462 if (valueset.getCompose().getInclude().size() == 1) { 463 return valueset.getCompose().getInclude().get(0).getSystem(); 464 } 465 466 return null; 467 } 468 469 private String getValueSetSystemOrNull() throws FHIRException { 470 if (valueset == null) { 471 return null; 472 } 473 if (valueset.getCompose().getInclude().size() == 0) { 474 if (!valueset.hasExpansion() || valueset.getExpansion().getContains().size() == 0) { 475 return null; 476 } else { 477 String cs = valueset.getExpansion().getContains().get(0).getSystem(); 478 if (cs != null && checkSystem(valueset.getExpansion().getContains(), cs)) { 479 return cs; 480 } else { 481 return null; 482 } 483 } 484 } 485 for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { 486 if (inc.hasValueSet()) { 487 return null; 488 } 489 if (!inc.hasSystem()) { 490 return null; 491 } 492 } 493 if (valueset.getCompose().getInclude().size() == 1) { 494 return valueset.getCompose().getInclude().get(0).getSystem(); 495 } 496 497 return null; 498 } 499 500 /* 501 * Check that all system values within an expansion correspond to the specified system value 502 */ 503 private boolean checkSystem(List<ValueSetExpansionContainsComponent> containsList, String system) { 504 for (ValueSetExpansionContainsComponent contains : containsList) { 505 if (!contains.getSystem().equals(system) || (contains.hasContains() && !checkSystem(contains.getContains(), system))) { 506 return false; 507 } 508 } 509 return true; 510 } 511 512 private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code) { 513 if (code.equals(concept.getCode())) { 514 return concept; 515 } 516 ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code); 517 if (cc != null) { 518 return cc; 519 } 520 if (concept.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) { 521 List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) concept.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK); 522 for (ConceptDefinitionComponent c : children) { 523 cc = findCodeInConcept(c, code); 524 if (cc != null) { 525 return cc; 526 } 527 } 528 } 529 return null; 530 } 531 532 private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code) { 533 for (ConceptDefinitionComponent cc : concept) { 534 if (code.equals(cc.getCode())) { 535 return cc; 536 } 537 ConceptDefinitionComponent c = findCodeInConcept(cc, code); 538 if (c != null) { 539 return c; 540 } 541 } 542 return null; 543 } 544 545 546 private String systemForCodeInValueSet(String code) { 547 String sys = null; 548 if (valueset.hasCompose()) { 549 if (valueset.getCompose().hasExclude()) { 550 return null; 551 } 552 for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { 553 if (vsi.hasValueSet()) { 554 return null; 555 } 556 if (!vsi.hasSystem()) { 557 return null; 558 } 559 if (vsi.hasFilter()) { 560 return null; 561 } 562 CodeSystem cs = resolveCodeSystem(vsi.getSystem()); 563 if (cs == null) { 564 return null; 565 } 566 if (vsi.hasConcept()) { 567 for (ConceptReferenceComponent cc : vsi.getConcept()) { 568 boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code); 569 if (match) { 570 if (sys == null) { 571 sys = vsi.getSystem(); 572 } else if (!sys.equals(vsi.getSystem())) { 573 return null; 574 } 575 } 576 } 577 } else { 578 ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code); 579 if (cc != null) { 580 if (sys == null) { 581 sys = vsi.getSystem(); 582 } else if (!sys.equals(vsi.getSystem())) { 583 return null; 584 } 585 } 586 } 587 } 588 } else if (valueset.hasExpansion()) { 589 // Retrieve a list of all systems associated with this code in the expansion 590 List<String> systems = new ArrayList<String>(); 591 checkSystems(valueset.getExpansion().getContains(), code, systems); 592 if (systems.size()==1) 593 sys = systems.get(0); 594 } 595 596 return sys; 597 } 598 599 /* 600 * Recursively go through all codes in the expansion and for any coding that matches the specified code, add the system for that coding 601 * to the passed list. 602 */ 603 private void checkSystems(List<ValueSetExpansionContainsComponent> contains, String code, List<String> systems) { 604 for (ValueSetExpansionContainsComponent c: contains) { 605 if (c.getCode().equals(code)) { 606 if (!systems.contains(c.getSystem())) 607 systems.add(c.getSystem()); 608 } 609 if (c.hasContains()) 610 checkSystems(c.getContains(), code, systems); 611 } 612 } 613 614 @Override 615 public Boolean codeInValueSet(String system, String code, List<String> warnings) throws FHIRException { 616 if (valueset == null) { 617 return false; 618 } 619 Boolean result = false; 620 621 if (valueset.hasExpansion()) { 622 return checkExpansion(new Coding(system, code, null)); 623 } else if (valueset.hasCompose()) { 624 int i = 0; 625 for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { 626 Boolean ok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); 627 i++; 628 if (ok == null && result == false) { 629 result = null; 630 } else if (ok) { 631 result = true; 632 break; 633 } 634 } 635 i = valueset.getCompose().getInclude().size(); 636 for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) { 637 Boolean nok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); 638 i++; 639 if (nok == null && result == false) { 640 result = null; 641 } else if (nok != null && nok) { 642 result = false; 643 } 644 } 645 } 646 647 return result; 648 } 649 650 private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, List<String> warnings) throws FHIRException { 651 for (UriType uri : vsi.getValueSet()) { 652 if (inImport(uri.getValue(), system, code)) { 653 return true; 654 } 655 } 656 657 if (!vsi.hasSystem()) { 658 return false; 659 } 660 if (only && system == null) { 661 // whether we know the system or not, we'll accept the stated codes at face value 662 for (ConceptReferenceComponent cc : vsi.getConcept()) { 663 if (cc.getCode().equals(code)) { 664 return true; 665 } 666 } 667 } 668 669 if (!system.equals(vsi.getSystem())) 670 return false; 671 // ok, we need the code system 672 CodeSystem cs = resolveCodeSystem(system); 673 if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)) { 674 // make up a transient value set with 675 ValueSet vs = new ValueSet(); 676 vs.setStatus(PublicationStatus.ACTIVE); 677 vs.setUrl(valueset.getUrl()+"--"+vsiIndex); 678 vs.setVersion(valueset.getVersion()); 679 vs.getCompose().addInclude(vsi); 680 ValidationResult res = context.validateCode(options.noClient(), new Coding(system, code, null), vs); 681 if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { 682 if (warnings != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { 683 warnings.add(context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system)); 684 } 685 return null; 686 } 687 if (res.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) { 688 throw new NoTerminologyServiceException(); 689 } 690 return res.isOk(); 691 } else { 692 if (vsi.hasFilter()) { 693 boolean ok = true; 694 for (ConceptSetFilterComponent f : vsi.getFilter()) { 695 if (!codeInFilter(cs, system, f, code)) { 696 ok = false; 697 break; 698 } 699 } 700 return ok; 701 } 702 703 List<ConceptDefinitionComponent> list = cs.getConcept(); 704 boolean ok = validateCodeInConceptList(code, cs, list); 705 if (ok && vsi.hasConcept()) { 706 for (ConceptReferenceComponent cc : vsi.getConcept()) { 707 if (cc.getCode().equals(code)) { 708 return true; 709 } 710 } 711 return false; 712 } else { 713 return ok; 714 } 715 } 716 } 717 718 private boolean codeInFilter(CodeSystem cs, String system, ConceptSetFilterComponent f, String code) throws FHIRException { 719 if ("concept".equals(f.getProperty())) 720 return codeInConceptFilter(cs, f, code); 721 else { 722 System.out.println("todo: handle filters with property = "+f.getProperty()); 723 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__FILTER_WITH_PROPERTY__, cs.getUrl(), f.getProperty())); 724 } 725 } 726 727 private boolean codeInConceptFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) throws FHIRException { 728 switch (f.getOp()) { 729 case ISA: return codeInConceptIsAFilter(cs, f, code, false); 730 case ISNOTA: return !codeInConceptIsAFilter(cs, f, code, false); 731 case DESCENDENTOF: return codeInConceptIsAFilter(cs, f, code, true); 732 default: 733 System.out.println("todo: handle concept filters with op = "+f.getOp()); 734 throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__CONCEPT_FILTER_WITH_OP__, cs.getUrl(), f.getOp())); 735 } 736 } 737 738 private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code, boolean rootOnly) { 739 if (!rootOnly && code.equals(f.getProperty())) { 740 return true; 741 } 742 ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue()); 743 if (cc == null) { 744 return false; 745 } 746 cc = findCodeInConcept(cc, code); 747 return cc != null; 748 } 749 750 public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list) { 751 if (def.getCaseSensitive()) { 752 for (ConceptDefinitionComponent cc : list) { 753 if (cc.getCode().equals(code)) { 754 return true; 755 } 756 if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept())) { 757 return true; 758 } 759 } 760 } else { 761 for (ConceptDefinitionComponent cc : list) { 762 if (cc.getCode().equalsIgnoreCase(code)) { 763 return true; 764 } 765 if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept())) { 766 return true; 767 } 768 } 769 } 770 return false; 771 } 772 773 private ValueSetCheckerSimple getVs(String url) { 774 if (inner.containsKey(url)) { 775 return inner.get(url); 776 } 777 ValueSet vs = context.fetchResource(ValueSet.class, url); 778 ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, context, localContext); 779 inner.put(url, vsc); 780 return vsc; 781 } 782 783 private boolean inImport(String uri, String system, String code) throws FHIRException { 784 ValueSetCheckerSimple vs = getVs(uri); 785 if (vs == null) { 786 return false; 787 } else { 788 Boolean ok = vs.codeInValueSet(system, code, null); 789 return ok != null && ok; 790 } 791 } 792 793}