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}