001package org.hl7.fhir.common.hapi.validation.support; 002 003import ca.uhn.fhir.i18n.Msg; 004import ca.uhn.fhir.context.FhirContext; 005import ca.uhn.fhir.context.FhirVersionEnum; 006import ca.uhn.fhir.context.support.ConceptValidationOptions; 007import ca.uhn.fhir.context.support.IValidationSupport; 008import ca.uhn.fhir.context.support.ValidationSupportContext; 009import ca.uhn.fhir.context.support.ValueSetExpansionOptions; 010import ca.uhn.fhir.parser.IParser; 011import ca.uhn.fhir.util.FhirVersionIndependentConcept; 012import org.apache.commons.lang3.Validate; 013import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50; 014import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_50; 015import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; 016import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; 017import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; 018import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; 019import org.hl7.fhir.dstu2.model.ValueSet; 020import org.hl7.fhir.instance.model.api.IBaseResource; 021import org.hl7.fhir.instance.model.api.IPrimitiveType; 022import org.hl7.fhir.r5.model.CanonicalType; 023import org.hl7.fhir.r5.model.CodeSystem; 024import org.hl7.fhir.utilities.validation.ValidationMessage; 025 026import javax.annotation.Nonnull; 027import javax.annotation.Nullable; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.HashSet; 031import java.util.List; 032import java.util.Objects; 033import java.util.Optional; 034import java.util.Set; 035import java.util.function.Consumer; 036import java.util.function.Function; 037import java.util.stream.Collectors; 038 039import static org.apache.commons.lang3.StringUtils.defaultString; 040import static org.apache.commons.lang3.StringUtils.isBlank; 041import static org.apache.commons.lang3.StringUtils.isNotBlank; 042 043/** 044 * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes 045 * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology 046 * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an 047 * external term service API) 048 */ 049public class InMemoryTerminologyServerValidationSupport implements IValidationSupport { 050 private final FhirContext myCtx; 051 052 public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) { 053 Validate.notNull(theCtx, "theCtx must not be null"); 054 myCtx = theCtx; 055 } 056 057 @Override 058 public FhirContext getFhirContext() { 059 return myCtx; 060 } 061 062 @Override 063 public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) { 064 return expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand, null, null); 065 } 066 067 private ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, String theWantSystemAndVersion, String theWantCode) { 068 org.hl7.fhir.r5.model.ValueSet expansionR5; 069 try { 070 expansionR5 = expandValueSetToCanonical(theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode); 071 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 072 return new ValueSetExpansionOutcome(e.getMessage()); 073 } 074 if (expansionR5 == null) { 075 return null; 076 } 077 078 IBaseResource expansion; 079 switch (myCtx.getVersion().getVersion()) { 080 case DSTU2_HL7ORG: { 081 expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false)); 082 break; 083 } 084 case DSTU3: { 085 expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false)); 086 break; 087 } 088 case R4: { 089 expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false)); 090 break; 091 } 092 case R5: { 093 expansion = expansionR5; 094 break; 095 } 096 case DSTU2: 097 case DSTU2_1: 098 default: 099 throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: " + myCtx.getVersion().getVersion()); 100 } 101 102 return new ValueSetExpansionOutcome(expansion); 103 } 104 105 private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 106 org.hl7.fhir.r5.model.ValueSet expansionR5; 107 switch (theValueSetToExpand.getStructureFhirVersionEnum()) { 108 case DSTU2: { 109 expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 110 break; 111 } 112 case DSTU2_HL7ORG: { 113 expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 114 break; 115 } 116 case DSTU3: { 117 expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 118 break; 119 } 120 case R4: { 121 expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 122 break; 123 } 124 case R5: { 125 expansionR5 = expandValueSetR5(theValidationSupportContext, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 126 break; 127 } 128 case DSTU2_1: 129 default: 130 throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: " + myCtx.getVersion().getVersion()); 131 } 132 133 return expansionR5; 134 } 135 136 @Override 137 public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { 138 org.hl7.fhir.r5.model.ValueSet expansion; 139 String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(theValueSet); 140 try { 141 expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); 142 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 143 CodeValidationResult codeValidationResult = new CodeValidationResult(); 144 codeValidationResult.setSeverityCode("error"); 145 146 String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + theCodeSystemUrlAndVersion + "#" + theCode; 147 if (e.getMessage() != null) { 148 msg += ". Error was: " + e.getMessage(); 149 } 150 151 codeValidationResult.setMessage(msg); 152 return codeValidationResult; 153 } 154 155 if (expansion == null) { 156 return null; 157 } 158 159 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion, vsUrl); 160 } 161 162 163 @Override 164 @Nullable 165 public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { 166 IBaseResource vs; 167 if (isNotBlank(theValueSetUrl)) { 168 vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); 169 if (vs == null) { 170 return null; 171 } 172 } else { 173 String codeSystemUrl; 174 String codeSystemVersion = null; 175 int codeSystemVersionIndex = theCodeSystem.indexOf("|"); 176 if (codeSystemVersionIndex > -1) { 177 codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex); 178 codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1); 179 } else { 180 codeSystemUrl = theCodeSystem; 181 } 182 switch (myCtx.getVersion().getVersion()) { 183 case DSTU2_HL7ORG: 184 vs = new org.hl7.fhir.dstu2.model.ValueSet() 185 .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() 186 .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 187 break; 188 case DSTU3: 189 if (codeSystemVersion != null) { 190 vs = new org.hl7.fhir.dstu3.model.ValueSet() 191 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 192 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 193 } else { 194 vs = new org.hl7.fhir.dstu3.model.ValueSet() 195 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 196 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 197 } 198 break; 199 case R4: 200 if (codeSystemVersion != null) { 201 vs = new org.hl7.fhir.r4.model.ValueSet() 202 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 203 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 204 } else { 205 vs = new org.hl7.fhir.r4.model.ValueSet() 206 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 207 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 208 } 209 break; 210 case R5: 211 if (codeSystemVersion != null) { 212 vs = new org.hl7.fhir.r5.model.ValueSet() 213 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 214 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 215 } else { 216 vs = new org.hl7.fhir.r5.model.ValueSet() 217 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 218 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 219 } 220 break; 221 case DSTU2: 222 case DSTU2_1: 223 default: 224 throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: " + myCtx.getVersion().getVersion()); 225 } 226 } 227 228 ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, null, vs, theCodeSystem, theCode); 229 if (valueSetExpansionOutcome == null) { 230 return null; 231 } 232 233 if (valueSetExpansionOutcome.getError() != null) { 234 return new CodeValidationResult() 235 .setSeverity(IssueSeverity.ERROR) 236 .setMessage(valueSetExpansionOutcome.getError()); 237 } 238 239 IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); 240 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl); 241 } 242 243 private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersionToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion, String theValueSetUrl) { 244 assert theExpansion != null; 245 246 boolean caseSensitive = true; 247 IBaseResource codeSystemToValidateResource = null; 248 if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { 249 codeSystemToValidateResource = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); 250 } 251 252 List<FhirVersionIndependentConcept> codes = new ArrayList<>(); 253 switch (theExpansion.getStructureFhirVersionEnum()) { 254 case DSTU2_HL7ORG: { 255 ValueSet expansionVs = (ValueSet) theExpansion; 256 List<ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 257 flattenAndConvertCodesDstu2(contains, codes); 258 break; 259 } 260 case DSTU3: { 261 org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; 262 List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 263 flattenAndConvertCodesDstu3(contains, codes); 264 break; 265 } 266 case R4: { 267 org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; 268 List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 269 flattenAndConvertCodesR4(contains, codes); 270 break; 271 } 272 case R5: { 273 org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; 274 List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 275 flattenAndConvertCodesR5(contains, codes); 276 break; 277 } 278 case DSTU2: 279 case DSTU2_1: 280 default: 281 throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: " + myCtx.getVersion().getVersion()); 282 } 283 284 String codeSystemResourceName = null; 285 String codeSystemResourceVersion = null; 286 String codeSystemResourceContentMode = null; 287 if (codeSystemToValidateResource != null) { 288 switch (codeSystemToValidateResource.getStructureFhirVersionEnum()) { 289 case DSTU2_HL7ORG: { 290 caseSensitive = true; 291 break; 292 } 293 case DSTU3: { 294 org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; 295 caseSensitive = systemDstu3.getCaseSensitive(); 296 codeSystemResourceName = systemDstu3.getName(); 297 codeSystemResourceVersion = systemDstu3.getVersion(); 298 codeSystemResourceContentMode = systemDstu3.getContentElement().getValueAsString(); 299 break; 300 } 301 case R4: { 302 org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource; 303 caseSensitive = systemR4.getCaseSensitive(); 304 codeSystemResourceName = systemR4.getName(); 305 codeSystemResourceVersion = systemR4.getVersion(); 306 codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); 307 break; 308 } 309 case R5: { 310 CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; 311 caseSensitive = systemR5.getCaseSensitive(); 312 codeSystemResourceName = systemR5.getName(); 313 codeSystemResourceVersion = systemR5.getVersion(); 314 codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); 315 break; 316 } 317 case DSTU2: 318 case DSTU2_1: 319 default: 320 throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: " + myCtx.getVersion().getVersion()); 321 } 322 } 323 324 String codeSystemUrlToValidate = null; 325 String codeSystemVersionToValidate = null; 326 if (theCodeSystemUrlAndVersionToValidate != null) { 327 int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); 328 if (versionIndex > -1) { 329 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); 330 codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1); 331 } else { 332 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; 333 } 334 } 335 for (FhirVersionIndependentConcept nextExpansionCode : codes) { 336 337 boolean codeMatches; 338 if (caseSensitive) { 339 codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); 340 } else { 341 codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); 342 } 343 if (codeMatches) { 344 if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { 345 String csVersion = codeSystemResourceVersion; 346 if (isNotBlank(nextExpansionCode.getSystemVersion())) { 347 csVersion = nextExpansionCode.getSystemVersion(); 348 } 349 if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { 350 CodeValidationResult codeValidationResult = new CodeValidationResult() 351 .setCode(theCodeToValidate) 352 .setDisplay(nextExpansionCode.getDisplay()) 353 .setCodeSystemName(codeSystemResourceName) 354 .setCodeSystemVersion(csVersion); 355 if (isNotBlank(theValueSetUrl)) { 356 codeValidationResult.setMessage("Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl); 357 } 358 return codeValidationResult; 359 } else { 360 String message = "Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\""; 361 if (isNotBlank(theValueSetUrl)) { 362 message += " for in-memory expansion of ValueSet: " + theValueSetUrl; 363 } 364 return new CodeValidationResult() 365 .setSeverity(IssueSeverity.ERROR) 366 .setDisplay(nextExpansionCode.getDisplay()) 367 .setMessage(message) 368 .setCodeSystemName(codeSystemResourceName) 369 .setCodeSystemVersion(csVersion); 370 } 371 } 372 } 373 } 374 375 ValidationMessage.IssueSeverity severity; 376 String message; 377 if ("fragment".equals(codeSystemResourceContentMode)) { 378 severity = ValidationMessage.IssueSeverity.WARNING; 379 message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; 380 } else { 381 severity = ValidationMessage.IssueSeverity.ERROR; 382 message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; 383 } 384 if (isNotBlank(theValueSetUrl)) { 385 message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'"; 386 } 387 388 return new CodeValidationResult() 389 .setSeverityCode(severity.toCode()) 390 .setMessage(message); 391 } 392 393 @Override 394 public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) { 395 CodeValidationResult codeValidationResult = validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); 396 if (codeValidationResult == null) { 397 return null; 398 } 399 return codeValidationResult.asLookupCodeResult(theSystem, theCode); 400 } 401 402 @Nullable 403 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 404 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false)); 405 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 406 } 407 408 @Nullable 409 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 410 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 411 IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); 412 413 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput)); 414 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); 415 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 416 } 417 418 @Override 419 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 420 if (isBlank(theSystem)) { 421 return false; 422 } 423 424 IBaseResource cs = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); 425 426 if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { 427 return cs != null; 428 } 429 430 if (cs != null) { 431 IPrimitiveType<?> content = getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); 432 return !"not-present".equals(content.getValueAsString()); 433 } 434 435 return false; 436 } 437 438 @Override 439 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 440 return isNotBlank(theValueSetUrl) && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; 441 } 442 443 444 private void addCodesDstu2Hl7Org(List<ValueSet.ConceptDefinitionComponent> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 445 for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { 446 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 447 theTargetList.add(targetConcept); 448 addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); 449 } 450 } 451 452 private void addCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 453 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { 454 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 455 theTargetList.add(targetConcept); 456 addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); 457 } 458 } 459 460 @Nullable 461 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 462 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false)); 463 return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode)); 464 } 465 466 @Nullable 467 private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 468 org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false)); 469 return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); 470 } 471 472 @Nullable 473 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) throws ExpansionCouldNotBeCompletedInternallyException { 474 return expandValueSetR5(theValidationSupportContext, theInput, null, null); 475 } 476 477 @Nullable 478 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 479 Set<FhirVersionIndependentConcept> concepts = new HashSet<>(); 480 481 expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode); 482 expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode); 483 484 org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); 485 for (FhirVersionIndependentConcept next : concepts) { 486 org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = retVal.getExpansion().addContains(); 487 contains.setSystem(next.getSystem()); 488 contains.setCode(next.getCode()); 489 contains.setDisplay(next.getDisplay()); 490 contains.setVersion(next.getSystemVersion()); 491 } 492 493 return retVal; 494 } 495 496 /** 497 * Use with caution - this is not a stable API 498 * 499 * @since 5.6.0 500 */ 501 public void expandValueSetIncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) throws ExpansionCouldNotBeCompletedInternallyException { 502 expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude); 503 } 504 505 506 private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Set<FhirVersionIndependentConcept> theConcepts, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 507 Consumer<FhirVersionIndependentConcept> consumer = c -> { 508 if (theComposeListIsInclude) { 509 theConcepts.add(c); 510 } else { 511 theConcepts.remove(c); 512 } 513 }; 514 expandValueSetR5IncludeOrExcludes(theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode); 515 } 516 517 518 private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 519 ExpansionCouldNotBeCompletedInternallyException caughtException = null; 520 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { 521 try { 522 boolean outcome = expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude); 523 if (isNotBlank(theWantCode)) { 524 if (outcome) { 525 return; 526 } 527 } 528 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 529 if (isBlank(theWantCode)) { 530 throw e; 531 } else { 532 caughtException = e; 533 } 534 } 535 } 536 if (caughtException != null) { 537 throw caughtException; 538 } 539 } 540 541 /** 542 * Returns <code>true</code> if at least one code was addded 543 */ 544 private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException { 545 String wantSystemUrl = null; 546 String wantSystemVersion = null; 547 if (theWantSystemUrlAndVersion != null) { 548 int versionIndex = theWantSystemUrlAndVersion.indexOf("|"); 549 if (versionIndex > -1) { 550 wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex); 551 wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1); 552 } else { 553 wantSystemUrl = theWantSystemUrlAndVersion; 554 } 555 } 556 557 Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext); 558 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = newValueSetLoader(theValidationSupportContext); 559 560 List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>(); 561 String includeOrExcludeConceptSystemUrl = theInclude.getSystem(); 562 String includeOrExcludeConceptSystemVersion = theInclude.getVersion(); 563 CodeSystem includeOrExcludeSystemResource = null; 564 if (isNotBlank(includeOrExcludeConceptSystemUrl)) { 565 566 if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { 567 return false; 568 } 569 570 if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { 571 return false; 572 } 573 574 String loadedCodeSystemUrl; 575 if (includeOrExcludeConceptSystemVersion != null) { 576 loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion; 577 } else { 578 loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl; 579 } 580 581 includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); 582 583 Set<String> wantCodes; 584 if (theInclude.getConcept().isEmpty()) { 585 wantCodes = null; 586 } else { 587 wantCodes = theInclude 588 .getConcept() 589 .stream().map(t -> t.getCode()).collect(Collectors.toSet()); 590 } 591 592 boolean ableToHandleCode = false; 593 String failureMessage = null; 594 FailureType failureType = FailureType.OTHER; 595 596 if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { 597 598 if (theWantCode != null) { 599 if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { 600 LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode, null); 601 if (lookup != null && lookup.isFound()) { 602 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 603 .addConcept() 604 .setCode(theWantCode) 605 .setDisplay(lookup.getCodeDisplay()); 606 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 607 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 608 ableToHandleCode = true; 609 } 610 } else { 611 612 /* 613 * If we're doing an expansion specifically looking for a single code, that means we're validating that code. 614 * In the case where we have a ValueSet that explicitly enumerates a collection of codes 615 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid 616 * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for 617 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that 618 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to 619 * enumerate a set of good codes for them is a nice compromise there. 620 */ 621 if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) { 622 Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = theInclude.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst(); 623 624 // If the ValueSet.compose.include has no individual concepts in it, and 625 // we can't find the actual referenced CodeSystem, we have no choice 626 // but to fail 627 if (!theInclude.getConcept().isEmpty()) { 628 ableToHandleCode = true; 629 } else { 630 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl); 631 } 632 633 if (matchingEnumeratedConcept.isPresent()) { 634 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 635 .addConcept() 636 .setCode(theWantCode) 637 .setDisplay(matchingEnumeratedConcept.get().getDisplay()); 638 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 639 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 640 } 641 } 642 643 } 644 } else { 645 if (isNotBlank(theInclude.getSystem()) && !theInclude.getConcept().isEmpty() && theInclude.getFilter().isEmpty() && theInclude.getValueSet().isEmpty()) { 646 theInclude 647 .getConcept() 648 .stream() 649 .map(t -> new FhirVersionIndependentConcept(theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) 650 .forEach(t -> nextCodeList.add(t)); 651 ableToHandleCode = true; 652 } 653 654 if (!ableToHandleCode) { 655 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl); 656 } 657 } 658 659 } else { 660 ableToHandleCode = true; 661 } 662 663 if (!ableToHandleCode) { 664 if (includeOrExcludeSystemResource == null && failureMessage == null) { 665 failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl); 666 } 667 668 if (includeOrExcludeSystemResource == null) { 669 failureType = FailureType.UNKNOWN_CODE_SYSTEM; 670 } 671 672 throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType); 673 } 674 675 if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { 676 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes); 677 } 678 679 } 680 681 for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) { 682 org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString()); 683 if (vs != null) { 684 org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode); 685 if (subExpansion == null) { 686 throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), FailureType.OTHER); 687 } 688 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { 689 nextCodeList.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 690 } 691 } 692 } 693 694 boolean retVal = false; 695 696 for (FhirVersionIndependentConcept next : nextCodeList) { 697 if (includeOrExcludeSystemResource != null && theWantCode != null) { 698 boolean matches; 699 if (includeOrExcludeSystemResource.getCaseSensitive()) { 700 matches = theWantCode.equals(next.getCode()); 701 } else { 702 matches = theWantCode.equalsIgnoreCase(next.getCode()); 703 } 704 if (!matches) { 705 continue; 706 } 707 } 708 709 theConsumer.accept(next); 710 retVal = true; 711 } 712 713 return retVal; 714 } 715 716 private Function<String, org.hl7.fhir.r5.model.ValueSet> newValueSetLoader(ValidationSupportContext theValidationSupportContext) { 717 switch (myCtx.getVersion().getVersion()) { 718 case DSTU2: 719 case DSTU2_HL7ORG: 720 return t -> { 721 IBaseResource vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 722 if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { 723 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 724 IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser(); 725 ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs; 726 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); 727 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false)); 728 } else { 729 org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 730 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false)); 731 } 732 }; 733 case DSTU3: 734 return t -> { 735 org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 736 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false)); 737 }; 738 case R4: 739 return t -> { 740 org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 741 return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false)); 742 }; 743 default: 744 case DSTU2_1: 745 case R5: 746 return t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 747 } 748 } 749 750 private Function<String, CodeSystem> newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) { 751 switch (myCtx.getVersion().getVersion()) { 752 case DSTU2: 753 case DSTU2_HL7ORG: 754 return t -> { 755 IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 756 CodeSystem retVal = null; 757 if (codeSystem != null) { 758 retVal = new CodeSystem(); 759 if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) { 760 ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem; 761 retVal.setUrl(codeSystemCasted.getUrl()); 762 addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); 763 } else { 764 org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = (org.hl7.fhir.dstu2.model.ValueSet) codeSystem; 765 retVal.setUrl(codeSystemCasted.getUrl()); 766 addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept()); 767 } 768 } 769 return retVal; 770 }; 771 case DSTU3: 772 return t -> { 773 org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 774 return (CodeSystem) VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false)); 775 }; 776 case R4: 777 return t -> { 778 org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 779 return (CodeSystem) VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false)); 780 }; 781 case DSTU2_1: 782 case R5: 783 default: 784 return t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 785 786 } 787 } 788 789 private String getFailureMessageForMissingOrUnusableCodeSystem(CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) { 790 String failureMessage; 791 if (includeOrExcludeSystemResource == null) { 792 failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl; 793 } else { 794 assert includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT; 795 failureMessage = "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " + loadedCodeSystemUrl; 796 } 797 return failureMessage; 798 } 799 800 private void addCodes(String theCodeSystemUrl, String theCodeSystemVersion, List<CodeSystem.ConceptDefinitionComponent> theSource, List<FhirVersionIndependentConcept> theTarget, Set<String> theCodeFilter) { 801 for (CodeSystem.ConceptDefinitionComponent next : theSource) { 802 if (isNotBlank(next.getCode())) { 803 if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { 804 theTarget.add(new FhirVersionIndependentConcept(theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); 805 } 806 } 807 addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); 808 } 809 } 810 811 812 public enum FailureType { 813 814 UNKNOWN_CODE_SYSTEM, 815 OTHER 816 817 } 818 819 public static class ExpansionCouldNotBeCompletedInternallyException extends Exception { 820 821 private static final long serialVersionUID = -2226561628771483085L; 822 private final FailureType myFailureType; 823 824 public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) { 825 super(theMessage); 826 myFailureType = theFailureType; 827 } 828 829 public FailureType getFailureType() { 830 return myFailureType; 831 } 832 } 833 834 private static void flattenAndConvertCodesDstu2(List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 835 for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 836 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 837 flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); 838 } 839 } 840 841 private static void flattenAndConvertCodesDstu3(List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 842 for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 843 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 844 flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); 845 } 846 } 847 848 private static void flattenAndConvertCodesR4(List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 849 for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 850 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 851 flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); 852 } 853 } 854 855 private static void flattenAndConvertCodesR5(List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 856 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 857 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 858 flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); 859 } 860 } 861 862}