001package org.hl7.fhir.common.hapi.validation.support; 002 003import ca.uhn.fhir.context.FhirContext; 004import ca.uhn.fhir.context.FhirVersionEnum; 005import ca.uhn.fhir.context.support.ConceptValidationOptions; 006import ca.uhn.fhir.context.support.IValidationSupport; 007import ca.uhn.fhir.context.support.ValidationSupportContext; 008import ca.uhn.fhir.context.support.ValueSetExpansionOptions; 009import ca.uhn.fhir.parser.IParser; 010import ca.uhn.fhir.util.VersionIndependentConcept; 011import org.apache.commons.lang3.Validate; 012import org.hl7.fhir.convertors.conv10_50.ValueSet10_50; 013import org.hl7.fhir.convertors.conv30_50.CodeSystem30_50; 014import org.hl7.fhir.convertors.conv30_50.ValueSet30_50; 015import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50; 016import org.hl7.fhir.convertors.conv40_50.ValueSet40_50; 017import org.hl7.fhir.dstu2.model.ValueSet; 018import org.hl7.fhir.instance.model.api.IBaseResource; 019import org.hl7.fhir.instance.model.api.IPrimitiveType; 020import org.hl7.fhir.r5.model.CanonicalType; 021import org.hl7.fhir.r5.model.CodeSystem; 022import org.hl7.fhir.utilities.validation.ValidationMessage; 023 024import javax.annotation.Nonnull; 025import javax.annotation.Nullable; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Objects; 031import java.util.Optional; 032import java.util.Set; 033import java.util.function.Function; 034import java.util.stream.Collectors; 035 036import static org.apache.commons.lang3.StringUtils.defaultString; 037import static org.apache.commons.lang3.StringUtils.isBlank; 038import static org.apache.commons.lang3.StringUtils.isNotBlank; 039 040/** 041 * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes 042 * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology 043 * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an 044 * external term service API) 045 */ 046public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { 047 private final FhirContext myCtx; 048 049 public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { 050 Validate.notNull(theCtx, "theCtx must not be null"); 051 myCtx = theCtx; 052 } 053 054 @Override 055 public FhirContext getFhirContext() { 056 return myCtx; 057 } 058 059 @Override 060 public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) { 061 062 org.hl7.fhir.r5.model.ValueSet expansionR5 = expandValueSetToCanonical(theValidationSupportContext, theValueSetToExpand, null, null); 063 if (expansionR5 == null) { 064 return null; 065 } 066 067 IBaseResource expansion; 068 switch (myCtx.getVersion().getVersion()) { 069 case DSTU2_HL7ORG: { 070 expansion = ValueSet10_50.convertValueSet(expansionR5); 071 break; 072 } 073 case DSTU3: { 074 expansion = ValueSet30_50.convertValueSet(expansionR5); 075 break; 076 } 077 case R4: { 078 expansion = ValueSet40_50.convertValueSet(expansionR5); 079 break; 080 } 081 case R5: { 082 expansion = expansionR5; 083 break; 084 } 085 case DSTU2: 086 case DSTU2_1: 087 default: 088 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 089 } 090 091 return new ValueSetExpansionOutcome(expansion, null); 092 } 093 094 private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystem, @Nullable String theWantCode) { 095 org.hl7.fhir.r5.model.ValueSet expansionR5; 096 switch (theValueSetToExpand.getStructureFhirVersionEnum()) { 097 case DSTU2: { 098 expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystem, theWantCode); 099 break; 100 } 101 case DSTU2_HL7ORG: { 102 expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystem, theWantCode); 103 break; 104 } 105 case DSTU3: { 106 expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystem, theWantCode); 107 break; 108 } 109 case R4: { 110 expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystem, theWantCode); 111 break; 112 } 113 case R5: { 114 expansionR5 = expandValueSetR5(theValidationSupportContext, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand); 115 break; 116 } 117 case DSTU2_1: 118 default: 119 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 120 } 121 122 if (expansionR5 == null) { 123 return null; 124 } 125 return expansionR5; 126 } 127 128 @Override 129 public CodeValidationResult 130 validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { 131 org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystem, theCode); 132 if (expansion == null) { 133 return null; 134 } 135 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion); 136 } 137 138 139 @Override 140 public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { 141 IBaseResource vs; 142 if (isNotBlank(theValueSetUrl)) { 143 vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); 144 if (vs == null) { 145 return null; 146 } 147 } else { 148 switch (myCtx.getVersion().getVersion()) { 149 case DSTU2_HL7ORG: 150 vs = new org.hl7.fhir.dstu2.model.ValueSet() 151 .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() 152 .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 153 break; 154 case DSTU3: 155 vs = new org.hl7.fhir.dstu3.model.ValueSet() 156 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 157 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 158 break; 159 case R4: 160 vs = new org.hl7.fhir.r4.model.ValueSet() 161 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 162 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 163 break; 164 case R5: 165 vs = new org.hl7.fhir.r5.model.ValueSet() 166 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 167 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 168 break; 169 case DSTU2: 170 case DSTU2_1: 171 default: 172 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 173 } 174 } 175 176 ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, null, vs); 177 if (valueSetExpansionOutcome == null) { 178 return null; 179 } 180 181 IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); 182 183 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion); 184 185 } 186 187 private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, IBaseResource theExpansion) { 188 assert theExpansion != null; 189 190 boolean caseSensitive = true; 191 IBaseResource system = null; 192 if (!theOptions.isInferSystem() && isNotBlank(theCodeSystem)) { 193 system = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem); 194 } 195 196 List<VersionIndependentConcept> codes = new ArrayList<>(); 197 switch (theExpansion.getStructureFhirVersionEnum()) { 198 case DSTU2_HL7ORG: { 199 ValueSet expansionVs = (ValueSet) theExpansion; 200 List<ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 201 flattenAndConvertCodesDstu2(contains, codes); 202 break; 203 } 204 case DSTU3: { 205 org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; 206 List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 207 flattenAndConvertCodesDstu3(contains, codes); 208 break; 209 } 210 case R4: { 211 org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; 212 List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 213 flattenAndConvertCodesR4(contains, codes); 214 break; 215 } 216 case R5: { 217 org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; 218 List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 219 flattenAndConvertCodesR5(contains, codes); 220 break; 221 } 222 case DSTU2: 223 case DSTU2_1: 224 default: 225 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 226 } 227 228 String codeSystemName = null; 229 String codeSystemVersion = null; 230 String codeSystemContentMode = null; 231 if (system != null) { 232 switch (system.getStructureFhirVersionEnum()) { 233 case DSTU2_HL7ORG: { 234 caseSensitive = true; 235 break; 236 } 237 case DSTU3: { 238 org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) system; 239 caseSensitive = systemDstu3.getCaseSensitive(); 240 codeSystemName = systemDstu3.getName(); 241 codeSystemVersion = systemDstu3.getVersion(); 242 codeSystemContentMode = systemDstu3.getContentElement().getValueAsString(); 243 break; 244 } 245 case R4: { 246 org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) system; 247 caseSensitive = systemR4.getCaseSensitive(); 248 codeSystemName = systemR4.getName(); 249 codeSystemVersion = systemR4.getVersion(); 250 codeSystemContentMode = systemR4.getContentElement().getValueAsString(); 251 break; 252 } 253 case R5: { 254 CodeSystem systemR5 = (CodeSystem) system; 255 caseSensitive = systemR5.getCaseSensitive(); 256 codeSystemName = systemR5.getName(); 257 codeSystemVersion = systemR5.getVersion(); 258 codeSystemContentMode = systemR5.getContentElement().getValueAsString(); 259 break; 260 } 261 case DSTU2: 262 case DSTU2_1: 263 default: 264 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 265 } 266 } 267 268 for (VersionIndependentConcept nextExpansionCode : codes) { 269 270 boolean codeMatches; 271 if (caseSensitive) { 272 codeMatches = defaultString(theCode).equals(nextExpansionCode.getCode()); 273 } else { 274 codeMatches = defaultString(theCode).equalsIgnoreCase(nextExpansionCode.getCode()); 275 } 276 if (codeMatches) { 277 if (theOptions.isInferSystem() || nextExpansionCode.getSystem().equals(theCodeSystem)) { 278 if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplay) || nextExpansionCode.getDisplay().equals(theDisplay))) { 279 return new CodeValidationResult() 280 .setCode(theCode) 281 .setDisplay(nextExpansionCode.getDisplay()) 282 .setCodeSystemName(codeSystemName) 283 .setCodeSystemVersion(codeSystemVersion); 284 } else { 285 return new CodeValidationResult() 286 .setSeverity(IssueSeverity.ERROR) 287 .setDisplay(nextExpansionCode.getDisplay()) 288 .setMessage("Concept Display \"" + theDisplay + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"") 289 .setCodeSystemName(codeSystemName) 290 .setCodeSystemVersion(codeSystemVersion); 291 } 292 } 293 } 294 } 295 296 ValidationMessage.IssueSeverity severity; 297 String message; 298 if ("fragment".equals(codeSystemContentMode)) { 299 severity = ValidationMessage.IssueSeverity.WARNING; 300 message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystem) ? theCodeSystem + "#" : "") + theCode + "'"; 301 } else { 302 severity = ValidationMessage.IssueSeverity.ERROR; 303 message = "Unknown code '" + (isNotBlank(theCodeSystem) ? theCodeSystem + "#" : "") + theCode + "'"; 304 } 305 306 return new CodeValidationResult() 307 .setSeverityCode(severity.toCode()) 308 .setMessage(message); 309 } 310 311 @Override 312 public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { 313 return validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode); 314 } 315 316 @Nullable 317 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { 318 Function<String, CodeSystem> codeSystemLoader = t -> { 319 org.hl7.fhir.dstu2.model.ValueSet codeSystem = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 320 CodeSystem retVal = new CodeSystem(); 321 addCodesDstu2Hl7Org(codeSystem.getCodeSystem().getConcept(), retVal.getConcept()); 322 return retVal; 323 }; 324 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 325 org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 326 return ValueSet10_50.convertValueSet(valueSet); 327 }; 328 329 org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(theInput); 330 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); 331 return (output); 332 } 333 334 @Nullable 335 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { 336 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 337 IParser parserHapi = FhirContext.forCached(FhirVersionEnum.DSTU2).newJsonParser(); 338 339 Function<String, CodeSystem> codeSystemLoader = t -> { 340// ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 341 ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = theInput; 342 CodeSystem retVal = null; 343 if (codeSystem != null) { 344 retVal = new CodeSystem(); 345 retVal.setUrl(codeSystem.getUrl()); 346 addCodesDstu2(codeSystem.getCodeSystem().getConcept(), retVal.getConcept()); 347 } 348 return retVal; 349 }; 350 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 351 ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 352 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); 353 return ValueSet10_50.convertValueSet(valueSetRi); 354 }; 355 356 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput)); 357 org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(valueSetRi); 358 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); 359 return (output); 360 } 361 362 @Override 363 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 364 if (isBlank(theSystem)) { 365 return false; 366 } 367 368 IBaseResource cs = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); 369 370 if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { 371 return cs != null; 372 } 373 374 if (cs != null) { 375 IPrimitiveType<?> content = getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); 376 if (!"not-present".equals(content.getValueAsString())) { 377 return true; 378 } 379 } 380 381 return false; 382 } 383 384 @Override 385 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 386 return isNotBlank(theValueSetUrl) && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; 387 } 388 389 390 private void addCodesDstu2Hl7Org(List<ValueSet.ConceptDefinitionComponent> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 391 for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { 392 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 393 theTargetList.add(targetConcept); 394 addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); 395 } 396 } 397 398 private void addCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 399 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { 400 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 401 theTargetList.add(targetConcept); 402 addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); 403 } 404 } 405 406 @Nullable 407 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { 408 Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> { 409 org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 410 return CodeSystem30_50.convertCodeSystem(codeSystem); 411 }; 412 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 413 org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 414 return ValueSet30_50.convertValueSet(valueSet); 415 }; 416 417 org.hl7.fhir.r5.model.ValueSet input = ValueSet30_50.convertValueSet(theInput); 418 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); 419 return (output); 420 } 421 422 @Nullable 423 private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { 424 Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> { 425 org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 426 return CodeSystem40_50.convertCodeSystem(codeSystem); 427 }; 428 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 429 org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 430 return ValueSet40_50.convertValueSet(valueSet); 431 }; 432 433 org.hl7.fhir.r5.model.ValueSet input = ValueSet40_50.convertValueSet(theInput); 434 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode); 435 return (output); 436 } 437 438 @Nullable 439 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) { 440 Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 441 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 442 443 return expandValueSetR5(theValidationSupportContext, theInput, codeSystemLoader, valueSetLoader, null, null); 444 } 445 446 @Nullable 447 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, Function<String, CodeSystem> theCodeSystemLoader, Function<String, org.hl7.fhir.r5.model.ValueSet> theValueSetLoader, @Nullable String theWantSystem, @Nullable String theWantCode) { 448 Set<VersionIndependentConcept> concepts = new HashSet<>(); 449 450 try { 451 expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystem, theWantCode); 452 expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystem, theWantCode); 453 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 454 return null; 455 } 456 457 org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); 458 for (VersionIndependentConcept next : concepts) { 459 org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = retVal.getExpansion().addContains(); 460 contains.setSystem(next.getSystem()); 461 contains.setCode(next.getCode()); 462 contains.setDisplay(next.getDisplay()); 463 } 464 465 return retVal; 466 } 467 468 private void expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Set<VersionIndependentConcept> theConcepts, Function<String, CodeSystem> theCodeSystemLoader, Function<String, org.hl7.fhir.r5.model.ValueSet> theValueSetLoader, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystem, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 469 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { 470 471 List<VersionIndependentConcept> nextCodeList = new ArrayList<>(); 472 String system = nextInclude.getSystem(); 473 if (isNotBlank(system)) { 474 475 if (theWantSystem != null && !theWantSystem.equals(system)) { 476 continue; 477 } 478 479 CodeSystem codeSystem = theCodeSystemLoader.apply(system); 480 481 Set<String> wantCodes; 482 if (nextInclude.getConcept().isEmpty()) { 483 wantCodes = null; 484 } else { 485 wantCodes = nextInclude 486 .getConcept() 487 .stream().map(t -> t.getCode()).collect(Collectors.toSet()); 488 } 489 490 boolean ableToHandleCode = false; 491 if (codeSystem == null || codeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { 492 493 if (theWantCode != null) { 494 if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, system)) { 495 LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, system, theWantCode); 496 if (lookup != null && lookup.isFound()) { 497 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 498 .addConcept() 499 .setCode(theWantCode) 500 .setDisplay(lookup.getCodeDisplay()); 501 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 502 addCodes(system, codesList, nextCodeList, wantCodes); 503 ableToHandleCode = true; 504 } 505 } else if (theComposeListIsInclude) { 506 507 /* 508 * If we're doing an expansion specifically looking for a single code, that means we're validating that code. 509 * In the case where we have a ValueSet that explicitly enumerates a collection of codes 510 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid 511 * even iof we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for 512 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that 513 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to 514 * enumerate a set of good codes for them is a nice compromise there. 515 */ 516 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent next : theComposeList) { 517 if (Objects.equals(next.getSystem(), theWantSystem)) { 518 Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = next.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst(); 519 if (matchingEnumeratedConcept.isPresent()) { 520 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 521 .addConcept() 522 .setCode(theWantCode) 523 .setDisplay(matchingEnumeratedConcept.get().getDisplay()); 524 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 525 addCodes(system, codesList, nextCodeList, wantCodes); 526 ableToHandleCode = true; 527 break; 528 } 529 } 530 } 531 532 } 533 } 534 535 } else { 536 ableToHandleCode = true; 537 } 538 539 if (!ableToHandleCode) { 540 throw new ExpansionCouldNotBeCompletedInternallyException(); 541 } 542 543 if (codeSystem != null && codeSystem.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { 544 addCodes(system, codeSystem.getConcept(), nextCodeList, wantCodes); 545 } 546 547 } 548 549 for (CanonicalType nextValueSetInclude : nextInclude.getValueSet()) { 550 org.hl7.fhir.r5.model.ValueSet vs = theValueSetLoader.apply(nextValueSetInclude.getValueAsString()); 551 if (vs != null) { 552 org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theCodeSystemLoader, theValueSetLoader, theWantSystem, theWantCode); 553 if (subExpansion == null) { 554 throw new ExpansionCouldNotBeCompletedInternallyException(); 555 } 556 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { 557 nextCodeList.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 558 } 559 } 560 } 561 562 if (theComposeListIsInclude) { 563 theConcepts.addAll(nextCodeList); 564 } else { 565 theConcepts.removeAll(nextCodeList); 566 } 567 568 } 569 570 } 571 572 private void addCodes(String theSystem, List<CodeSystem.ConceptDefinitionComponent> theSource, List<VersionIndependentConcept> theTarget, Set<String> theCodeFilter) { 573 for (CodeSystem.ConceptDefinitionComponent next : theSource) { 574 if (isNotBlank(next.getCode())) { 575 if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { 576 theTarget.add(new VersionIndependentConcept(theSystem, next.getCode(), next.getDisplay())); 577 } 578 } 579 addCodes(theSystem, next.getConcept(), theTarget, theCodeFilter); 580 } 581 } 582 583 private static class ExpansionCouldNotBeCompletedInternallyException extends Exception { 584 585 } 586 587 private static void flattenAndConvertCodesDstu2(List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<VersionIndependentConcept> theVersionIndependentConcepts) { 588 for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 589 theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 590 flattenAndConvertCodesDstu2(next.getContains(), theVersionIndependentConcepts); 591 } 592 } 593 594 private static void flattenAndConvertCodesDstu3(List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<VersionIndependentConcept> theVersionIndependentConcepts) { 595 for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 596 theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 597 flattenAndConvertCodesDstu3(next.getContains(), theVersionIndependentConcepts); 598 } 599 } 600 601 private static void flattenAndConvertCodesR4(List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<VersionIndependentConcept> theVersionIndependentConcepts) { 602 for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 603 theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 604 flattenAndConvertCodesR4(next.getContains(), theVersionIndependentConcepts); 605 } 606 } 607 608 private static void flattenAndConvertCodesR5(List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<VersionIndependentConcept> theVersionIndependentConcepts) { 609 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 610 theVersionIndependentConcepts.add(new VersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 611 flattenAndConvertCodesR5(next.getContains(), theVersionIndependentConcepts); 612 } 613 } 614 615}