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