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}