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.FhirVersionIndependentConcept; 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 theWantSystemUrlAndVersion, @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, theWantSystemUrlAndVersion, theWantCode); 099 break; 100 } 101 case DSTU2_HL7ORG: { 102 expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 103 break; 104 } 105 case DSTU3: { 106 expansionR5 = expandValueSetDstu3(theValidationSupportContext, (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode); 107 break; 108 } 109 case R4: { 110 expansionR5 = expandValueSetR4(theValidationSupportContext, (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, 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 return expansionR5; 123 } 124 125 @Override 126 public CodeValidationResult 127 validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { 128 org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); 129 if (expansion == null) { 130 return null; 131 } 132 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion); 133 } 134 135 136 @Override 137 public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { 138 IBaseResource vs; 139 if (isNotBlank(theValueSetUrl)) { 140 vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); 141 if (vs == null) { 142 return null; 143 } 144 } else { 145 String codeSystemUrl; 146 String codeSystemVersion = null; 147 int codeSystemVersionIndex = theCodeSystem.indexOf("|"); 148 if (codeSystemVersionIndex > -1) { 149 codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex); 150 codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1); 151 } else { 152 codeSystemUrl = theCodeSystem; 153 } 154 switch (myCtx.getVersion().getVersion()) { 155 case DSTU2_HL7ORG: 156 vs = new org.hl7.fhir.dstu2.model.ValueSet() 157 .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent() 158 .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 159 break; 160 case DSTU3: 161 if (codeSystemVersion != null) { 162 vs = new org.hl7.fhir.dstu3.model.ValueSet() 163 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 164 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 165 } else { 166 vs = new org.hl7.fhir.dstu3.model.ValueSet() 167 .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent() 168 .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 169 } 170 break; 171 case R4: 172 if (codeSystemVersion != null) { 173 vs = new org.hl7.fhir.r4.model.ValueSet() 174 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 175 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 176 } else { 177 vs = new org.hl7.fhir.r4.model.ValueSet() 178 .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent() 179 .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 180 } 181 break; 182 case R5: 183 if (codeSystemVersion != null) { 184 vs = new org.hl7.fhir.r5.model.ValueSet() 185 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 186 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(codeSystemUrl).setVersion(codeSystemVersion))); 187 } else { 188 vs = new org.hl7.fhir.r5.model.ValueSet() 189 .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent() 190 .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent().setSystem(theCodeSystem))); 191 } 192 break; 193 case DSTU2: 194 case DSTU2_1: 195 default: 196 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 197 } 198 } 199 200 ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, null, vs); 201 if (valueSetExpansionOutcome == null) { 202 return null; 203 } 204 205 IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); 206 207 return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion); 208 209 } 210 211 private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersionToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion) { 212 assert theExpansion != null; 213 214 boolean caseSensitive = true; 215 IBaseResource codeSystemToValidateResource = null; 216 if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) { 217 codeSystemToValidateResource = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystemUrlAndVersionToValidate); 218 } 219 220 List<FhirVersionIndependentConcept> codes = new ArrayList<>(); 221 switch (theExpansion.getStructureFhirVersionEnum()) { 222 case DSTU2_HL7ORG: { 223 ValueSet expansionVs = (ValueSet) theExpansion; 224 List<ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 225 flattenAndConvertCodesDstu2(contains, codes); 226 break; 227 } 228 case DSTU3: { 229 org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion; 230 List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 231 flattenAndConvertCodesDstu3(contains, codes); 232 break; 233 } 234 case R4: { 235 org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion; 236 List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 237 flattenAndConvertCodesR4(contains, codes); 238 break; 239 } 240 case R5: { 241 org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion; 242 List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains = expansionVs.getExpansion().getContains(); 243 flattenAndConvertCodesR5(contains, codes); 244 break; 245 } 246 case DSTU2: 247 case DSTU2_1: 248 default: 249 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 250 } 251 252 String codeSystemResourceName = null; 253 String codeSystemResourceVersion = null; 254 String codeSystemResourceContentMode = null; 255 if (codeSystemToValidateResource != null) { 256 switch (codeSystemToValidateResource.getStructureFhirVersionEnum()) { 257 case DSTU2_HL7ORG: { 258 caseSensitive = true; 259 break; 260 } 261 case DSTU3: { 262 org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 = (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource; 263 caseSensitive = systemDstu3.getCaseSensitive(); 264 codeSystemResourceName = systemDstu3.getName(); 265 codeSystemResourceVersion = systemDstu3.getVersion(); 266 codeSystemResourceContentMode = systemDstu3.getContentElement().getValueAsString(); 267 break; 268 } 269 case R4: { 270 org.hl7.fhir.r4.model.CodeSystem systemR4 = (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource; 271 caseSensitive = systemR4.getCaseSensitive(); 272 codeSystemResourceName = systemR4.getName(); 273 codeSystemResourceVersion = systemR4.getVersion(); 274 codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString(); 275 break; 276 } 277 case R5: { 278 CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource; 279 caseSensitive = systemR5.getCaseSensitive(); 280 codeSystemResourceName = systemR5.getName(); 281 codeSystemResourceVersion = systemR5.getVersion(); 282 codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString(); 283 break; 284 } 285 case DSTU2: 286 case DSTU2_1: 287 default: 288 throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 289 } 290 } 291 292 String codeSystemUrlToValidate=null; 293 String codeSystemVersionToValidate=null; 294 if (theCodeSystemUrlAndVersionToValidate != null) { 295 int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); 296 if (versionIndex > -1) { 297 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); 298 codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex+1); 299 } else { 300 codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; 301 } 302 } 303 for (FhirVersionIndependentConcept nextExpansionCode : codes) { 304 305 boolean codeMatches; 306 if (caseSensitive) { 307 codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode()); 308 } else { 309 codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode()); 310 } 311 if (codeMatches) { 312 if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { 313 if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) { 314 return new CodeValidationResult() 315 .setCode(theCodeToValidate) 316 .setDisplay(nextExpansionCode.getDisplay()) 317 .setCodeSystemName(codeSystemResourceName) 318 .setCodeSystemVersion(codeSystemResourceVersion); 319 } else { 320 return new CodeValidationResult() 321 .setSeverity(IssueSeverity.ERROR) 322 .setDisplay(nextExpansionCode.getDisplay()) 323 .setMessage("Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"") 324 .setCodeSystemName(codeSystemResourceName) 325 .setCodeSystemVersion(codeSystemResourceVersion); 326 } 327 } 328 } 329 } 330 331 ValidationMessage.IssueSeverity severity; 332 String message; 333 if ("fragment".equals(codeSystemResourceContentMode)) { 334 severity = ValidationMessage.IssueSeverity.WARNING; 335 message = "Unknown code in fragment CodeSystem '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; 336 } else { 337 severity = ValidationMessage.IssueSeverity.ERROR; 338 message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; 339 } 340 341 return new CodeValidationResult() 342 .setSeverityCode(severity.toCode()) 343 .setMessage(message); 344 } 345 346 @Override 347 public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { 348 return validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode); 349 } 350 351 @Nullable 352 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { 353 Function<String, CodeSystem> codeSystemLoader = t -> { 354 org.hl7.fhir.dstu2.model.ValueSet codeSystem = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 355 CodeSystem retVal = new CodeSystem(); 356 addCodesDstu2Hl7Org(codeSystem.getCodeSystem().getConcept(), retVal.getConcept()); 357 return retVal; 358 }; 359 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 360 org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 361 return ValueSet10_50.convertValueSet(valueSet); 362 }; 363 364 org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(theInput); 365 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode); 366 return (output); 367 } 368 369 @Nullable 370 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { 371 IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser(); 372 IParser parserHapi = FhirContext.forCached(FhirVersionEnum.DSTU2).newJsonParser(); 373 374 Function<String, CodeSystem> codeSystemLoader = t -> { 375// ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 376 ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = theInput; 377 CodeSystem retVal = null; 378 if (codeSystem != null) { 379 retVal = new CodeSystem(); 380 retVal.setUrl(codeSystem.getUrl()); 381 addCodesDstu2(codeSystem.getCodeSystem().getConcept(), retVal.getConcept()); 382 } 383 return retVal; 384 }; 385 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 386 ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 387 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet)); 388 return ValueSet10_50.convertValueSet(valueSetRi); 389 }; 390 391 org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput)); 392 org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(valueSetRi); 393 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode); 394 return (output); 395 } 396 397 @Override 398 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 399 if (isBlank(theSystem)) { 400 return false; 401 } 402 403 IBaseResource cs = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem); 404 405 if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) { 406 return cs != null; 407 } 408 409 if (cs != null) { 410 IPrimitiveType<?> content = getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class); 411 if (!"not-present".equals(content.getValueAsString())) { 412 return true; 413 } 414 } 415 416 return false; 417 } 418 419 @Override 420 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 421 return isNotBlank(theValueSetUrl) && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null; 422 } 423 424 425 private void addCodesDstu2Hl7Org(List<ValueSet.ConceptDefinitionComponent> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 426 for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) { 427 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 428 theTargetList.add(targetConcept); 429 addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept()); 430 } 431 } 432 433 private void addCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) { 434 for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) { 435 CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay()); 436 theTargetList.add(targetConcept); 437 addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept()); 438 } 439 } 440 441 @Nullable 442 private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { 443 Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> { 444 org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 445 return CodeSystem30_50.convertCodeSystem(codeSystem); 446 }; 447 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 448 org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 449 return ValueSet30_50.convertValueSet(valueSet); 450 }; 451 452 org.hl7.fhir.r5.model.ValueSet input = ValueSet30_50.convertValueSet(theInput); 453 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode); 454 return (output); 455 } 456 457 @Nullable 458 private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { 459 Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> { 460 org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 461 return CodeSystem40_50.convertCodeSystem(codeSystem); 462 }; 463 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> { 464 org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 465 return ValueSet40_50.convertValueSet(valueSet); 466 }; 467 468 org.hl7.fhir.r5.model.ValueSet input = ValueSet40_50.convertValueSet(theInput); 469 org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode); 470 return (output); 471 } 472 473 @Nullable 474 private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) { 475 Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); 476 Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t); 477 478 return expandValueSetR5(theValidationSupportContext, theInput, codeSystemLoader, valueSetLoader, null, null); 479 } 480 481 @Nullable 482 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 theWantSystemUrlAndVersion, @Nullable String theWantCode) { 483 Set<FhirVersionIndependentConcept> concepts = new HashSet<>(); 484 485 try { 486 expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode); 487 expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode); 488 } catch (ExpansionCouldNotBeCompletedInternallyException e) { 489 return null; 490 } 491 492 org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); 493 for (FhirVersionIndependentConcept next : concepts) { 494 org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains = retVal.getExpansion().addContains(); 495 contains.setSystem(next.getSystem()); 496 contains.setCode(next.getCode()); 497 contains.setDisplay(next.getDisplay()); 498 contains.setVersion(next.getSystemVersion()); 499 } 500 501 return retVal; 502 } 503 504 private void expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Set<FhirVersionIndependentConcept> 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 theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException { 505 String wantSystemUrl = null; 506 String wantSystemVersion = null; 507 if (theWantSystemUrlAndVersion != null) { 508 int versionIndex = theWantSystemUrlAndVersion.indexOf("|"); 509 if (versionIndex > -1) { 510 wantSystemUrl = theWantSystemUrlAndVersion.substring(0,versionIndex); 511 wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex+1); 512 } else { 513 wantSystemUrl = theWantSystemUrlAndVersion; 514 } 515 } 516 517 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) { 518 519 List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>(); 520 String includeOrExcludeConceptSystemUrl = nextInclude.getSystem(); 521 String includeOrExcludeConceptSystemVersion = nextInclude.getVersion(); 522 if (isNotBlank(includeOrExcludeConceptSystemUrl)) { 523 524 if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { 525 continue; 526 } 527 528 if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { 529 continue; 530 } 531 532 CodeSystem includeOrExcludeSystemResource; 533 if (includeOrExcludeConceptSystemVersion != null) { 534 includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion); 535 } else { 536 includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl); 537 } 538 539 Set<String> wantCodes; 540 if (nextInclude.getConcept().isEmpty()) { 541 wantCodes = null; 542 } else { 543 wantCodes = nextInclude 544 .getConcept() 545 .stream().map(t -> t.getCode()).collect(Collectors.toSet()); 546 } 547 548 boolean ableToHandleCode = false; 549 if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { 550 551 if (theWantCode != null) { 552 if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { 553 LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode); 554 if (lookup != null && lookup.isFound()) { 555 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 556 .addConcept() 557 .setCode(theWantCode) 558 .setDisplay(lookup.getCodeDisplay()); 559 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 560 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 561 ableToHandleCode = true; 562 } 563 } else if (theComposeListIsInclude) { 564 565 /* 566 * If we're doing an expansion specifically looking for a single code, that means we're validating that code. 567 * In the case where we have a ValueSet that explicitly enumerates a collection of codes 568 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid 569 * even iof we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for 570 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that 571 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to 572 * enumerate a set of good codes for them is a nice compromise there. 573 */ 574 for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent next : theComposeList) { 575 if (Objects.equals(next.getSystem(), theWantSystemUrlAndVersion)) { 576 Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = next.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst(); 577 if (matchingEnumeratedConcept.isPresent()) { 578 CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() 579 .addConcept() 580 .setCode(theWantCode) 581 .setDisplay(matchingEnumeratedConcept.get().getDisplay()); 582 List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); 583 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 584 ableToHandleCode = true; 585 break; 586 } 587 } 588 } 589 590 } 591 } 592 593 } else { 594 ableToHandleCode = true; 595 } 596 597 if (!ableToHandleCode) { 598 throw new ExpansionCouldNotBeCompletedInternallyException(); 599 } 600 601 if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) { 602 addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes); 603 } 604 605 } 606 607 for (CanonicalType nextValueSetInclude : nextInclude.getValueSet()) { 608 org.hl7.fhir.r5.model.ValueSet vs = theValueSetLoader.apply(nextValueSetInclude.getValueAsString()); 609 if (vs != null) { 610 org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theCodeSystemLoader, theValueSetLoader, theWantSystemUrlAndVersion, theWantCode); 611 if (subExpansion == null) { 612 throw new ExpansionCouldNotBeCompletedInternallyException(); 613 } 614 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) { 615 nextCodeList.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 616 } 617 } 618 } 619 620 if (theComposeListIsInclude) { 621 theConcepts.addAll(nextCodeList); 622 } else { 623 theConcepts.removeAll(nextCodeList); 624 } 625 626 } 627 628 } 629 630 private void addCodes(String theCodeSystemUrl, String theCodeSystemVersion, List<CodeSystem.ConceptDefinitionComponent> theSource, List<FhirVersionIndependentConcept> theTarget, Set<String> theCodeFilter) { 631 for (CodeSystem.ConceptDefinitionComponent next : theSource) { 632 if (isNotBlank(next.getCode())) { 633 if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) { 634 theTarget.add(new FhirVersionIndependentConcept(theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion)); 635 } 636 } 637 addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter); 638 } 639 } 640 641 private static class ExpansionCouldNotBeCompletedInternallyException extends Exception { 642 643 } 644 645 private static void flattenAndConvertCodesDstu2(List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 646 for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 647 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay())); 648 flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts); 649 } 650 } 651 652 private static void flattenAndConvertCodesDstu3(List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 653 for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 654 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 655 flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts); 656 } 657 } 658 659 private static void flattenAndConvertCodesR4(List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 660 for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 661 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 662 flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts); 663 } 664 } 665 666 private static void flattenAndConvertCodesR5(List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) { 667 for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) { 668 theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion())); 669 flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts); 670 } 671 } 672 673}