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