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.LookupCodeRequest;
008import ca.uhn.fhir.context.support.ValidationSupportContext;
009import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
010import ca.uhn.fhir.i18n.Msg;
011import ca.uhn.fhir.parser.IParser;
012import ca.uhn.fhir.util.FhirVersionIndependentConcept;
013import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
014import jakarta.annotation.Nonnull;
015import jakarta.annotation.Nullable;
016import org.apache.commons.lang3.Validate;
017import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50;
018import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_50;
019import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
020import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_43_50;
021import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
022import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
023import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
024import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50;
025import org.hl7.fhir.dstu2.model.ValueSet;
026import org.hl7.fhir.instance.model.api.IBaseResource;
027import org.hl7.fhir.instance.model.api.IPrimitiveType;
028import org.hl7.fhir.r5.model.CanonicalType;
029import org.hl7.fhir.r5.model.CodeSystem;
030import org.hl7.fhir.r5.model.Enumerations;
031import org.hl7.fhir.utilities.validation.ValidationMessage;
032
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.HashSet;
036import java.util.List;
037import java.util.Objects;
038import java.util.Optional;
039import java.util.Set;
040import java.util.function.Consumer;
041import java.util.function.Function;
042import java.util.stream.Collectors;
043
044import static org.apache.commons.lang3.StringUtils.contains;
045import static org.apache.commons.lang3.StringUtils.defaultString;
046import static org.apache.commons.lang3.StringUtils.isBlank;
047import static org.apache.commons.lang3.StringUtils.isNotBlank;
048import static org.apache.commons.lang3.StringUtils.substringAfter;
049import static org.apache.commons.lang3.StringUtils.substringBefore;
050import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.getFhirVersionEnum;
051
052/**
053 * This class is a basic in-memory terminology service, designed to expand ValueSets and validate codes
054 * completely in-memory. It is suitable for runtime validation purposes where no dedicated terminology
055 * service exists (either an internal one such as the HAPI FHIR JPA terminology service, or an
056 * external term service API)
057 */
058@SuppressWarnings("EnhancedSwitchMigration")
059public class InMemoryTerminologyServerValidationSupport implements IValidationSupport {
060        private static final String OUR_PIPE_CHARACTER = "|";
061        private final FhirContext myCtx;
062        private final VersionCanonicalizer myVersionCanonicalizer;
063        private IssueSeverity myIssueSeverityForCodeDisplayMismatch = IssueSeverity.WARNING;
064
065        /**
066         * Constructor
067         *
068         * @param theCtx A FhirContext for the FHIR version being validated
069         */
070        public InMemoryTerminologyServerValidationSupport(FhirContext theCtx) {
071                Validate.notNull(theCtx, "theCtx must not be null");
072                myCtx = theCtx;
073                myVersionCanonicalizer = new VersionCanonicalizer(theCtx);
074        }
075
076        @Override
077        public String getName() {
078                return myCtx.getVersion().getVersion() + " In-Memory Validation Support";
079        }
080
081        /**
082         * This setting controls the validation issue severity to report when a code validation
083         * finds that the code is present in the given CodeSystem, but the display name being
084         * validated doesn't match the expected value(s). Defaults to
085         * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this
086         * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION}
087         * if you don't want to see display name validation issues at all in resource validation
088         * outcomes.
089         *
090         * @since 7.0.0
091         */
092        public IssueSeverity getIssueSeverityForCodeDisplayMismatch() {
093                return myIssueSeverityForCodeDisplayMismatch;
094        }
095
096        /**
097         * This setting controls the validation issue severity to report when a code validation
098         * finds that the code is present in the given CodeSystem, but the display name being
099         * validated doesn't match the expected value(s). Defaults to
100         * {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#WARNING}. Set this
101         * value to {@link ca.uhn.fhir.context.support.IValidationSupport.IssueSeverity#INFORMATION}
102         * if you don't want to see display name validation issues at all in resource validation
103         * outcomes.
104         *
105         * @param theIssueSeverityForCodeDisplayMismatch The severity. Must not be {@literal null}.
106         * @since 7.0.0
107         */
108        public void setIssueSeverityForCodeDisplayMismatch(@Nonnull IssueSeverity theIssueSeverityForCodeDisplayMismatch) {
109                Validate.notNull(
110                                theIssueSeverityForCodeDisplayMismatch, "theIssueSeverityForCodeDisplayMismatch must not be null");
111                myIssueSeverityForCodeDisplayMismatch = theIssueSeverityForCodeDisplayMismatch;
112        }
113
114        @Override
115        public FhirContext getFhirContext() {
116                return myCtx;
117        }
118
119        @Override
120        public ValueSetExpansionOutcome expandValueSet(
121                        ValidationSupportContext theValidationSupportContext,
122                        ValueSetExpansionOptions theExpansionOptions,
123                        @Nonnull IBaseResource theValueSetToExpand) {
124                return expandValueSet(theValidationSupportContext, theValueSetToExpand, null, null);
125        }
126
127        private ValueSetExpansionOutcome expandValueSet(
128                        ValidationSupportContext theValidationSupportContext,
129                        IBaseResource theValueSetToExpand,
130                        String theWantSystemAndVersion,
131                        String theWantCode) {
132                org.hl7.fhir.r5.model.ValueSet expansionR5;
133                try {
134                        expansionR5 = expandValueSetToCanonical(
135                                        theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode);
136                } catch (ExpansionCouldNotBeCompletedInternallyException e) {
137                        return new ValueSetExpansionOutcome(e.getMessage());
138                }
139                if (expansionR5 == null) {
140                        return null;
141                }
142
143                IBaseResource expansion;
144                switch (myCtx.getVersion().getVersion()) {
145                        case DSTU2: {
146                                org.hl7.fhir.r4.model.ValueSet expansionR4 = (org.hl7.fhir.r4.model.ValueSet)
147                                                VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false));
148                                expansion = myVersionCanonicalizer.valueSetFromCanonical(expansionR4);
149                                break;
150                        }
151                        case DSTU2_HL7ORG: {
152                                expansion = VersionConvertorFactory_10_50.convertResource(expansionR5, new BaseAdvisor_10_50(false));
153                                break;
154                        }
155                        case DSTU3: {
156                                expansion = VersionConvertorFactory_30_50.convertResource(expansionR5, new BaseAdvisor_30_50(false));
157                                break;
158                        }
159                        case R4: {
160                                expansion = VersionConvertorFactory_40_50.convertResource(expansionR5, new BaseAdvisor_40_50(false));
161                                break;
162                        }
163                        case R4B: {
164                                expansion = VersionConvertorFactory_43_50.convertResource(expansionR5, new BaseAdvisor_43_50(false));
165                                break;
166                        }
167                        case R5: {
168                                expansion = expansionR5;
169                                break;
170                        }
171                        case DSTU2_1:
172                        default:
173                                throw new IllegalArgumentException(Msg.code(697) + "Can not handle version: "
174                                                + myCtx.getVersion().getVersion());
175                }
176
177                return new ValueSetExpansionOutcome(expansion);
178        }
179
180        private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(
181                        ValidationSupportContext theValidationSupportContext,
182                        IBaseResource theValueSetToExpand,
183                        @Nullable String theWantSystemUrlAndVersion,
184                        @Nullable String theWantCode)
185                        throws ExpansionCouldNotBeCompletedInternallyException {
186                org.hl7.fhir.r5.model.ValueSet expansionR5;
187                switch (getFhirVersionEnum(
188                                theValidationSupportContext.getRootValidationSupport().getFhirContext(), theValueSetToExpand)) {
189                        case DSTU2: {
190                                expansionR5 = expandValueSetDstu2(
191                                                theValidationSupportContext,
192                                                (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand,
193                                                theWantSystemUrlAndVersion,
194                                                theWantCode);
195                                break;
196                        }
197                        case DSTU2_HL7ORG: {
198                                expansionR5 = expandValueSetDstu2Hl7Org(
199                                                theValidationSupportContext,
200                                                (ValueSet) theValueSetToExpand,
201                                                theWantSystemUrlAndVersion,
202                                                theWantCode);
203                                break;
204                        }
205                        case DSTU3: {
206                                expansionR5 = expandValueSetDstu3(
207                                                theValidationSupportContext,
208                                                (org.hl7.fhir.dstu3.model.ValueSet) theValueSetToExpand,
209                                                theWantSystemUrlAndVersion,
210                                                theWantCode);
211                                break;
212                        }
213                        case R4: {
214                                expansionR5 = expandValueSetR4(
215                                                theValidationSupportContext,
216                                                (org.hl7.fhir.r4.model.ValueSet) theValueSetToExpand,
217                                                theWantSystemUrlAndVersion,
218                                                theWantCode);
219                                break;
220                        }
221                        case R4B: {
222                                expansionR5 = expandValueSetR4B(
223                                                theValidationSupportContext,
224                                                (org.hl7.fhir.r4b.model.ValueSet) theValueSetToExpand,
225                                                theWantSystemUrlAndVersion,
226                                                theWantCode);
227                                break;
228                        }
229                        case R5: {
230                                expansionR5 = expandValueSetR5(
231                                                theValidationSupportContext,
232                                                (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand,
233                                                theWantSystemUrlAndVersion,
234                                                theWantCode);
235                                break;
236                        }
237                        case DSTU2_1:
238                        default:
239                                throw new IllegalArgumentException(Msg.code(698) + "Can not handle version: "
240                                                + myCtx.getVersion().getVersion());
241                }
242
243                return expansionR5;
244        }
245
246        @Override
247        public CodeValidationResult validateCodeInValueSet(
248                        ValidationSupportContext theValidationSupportContext,
249                        ConceptValidationOptions theOptions,
250                        String theCodeSystemUrlAndVersion,
251                        String theCode,
252                        String theDisplay,
253                        @Nonnull IBaseResource theValueSet) {
254                org.hl7.fhir.r5.model.ValueSet expansion;
255                String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(getFhirContext(), theValueSet);
256                try {
257                        expansion = expandValueSetToCanonical(
258                                        theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode);
259                } catch (ExpansionCouldNotBeCompletedInternallyException e) {
260                        CodeValidationResult codeValidationResult = new CodeValidationResult();
261                        codeValidationResult.setSeverityCode("error");
262
263                        String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code "
264                                        + theCodeSystemUrlAndVersion + "#" + theCode;
265                        if (e.getMessage() != null) {
266                                msg += ". Error was: " + e.getMessage();
267                        }
268
269                        codeValidationResult.setMessage(msg);
270                        return codeValidationResult;
271                }
272
273                if (expansion == null) {
274                        return null;
275                }
276
277                return validateCodeInExpandedValueSet(
278                                theValidationSupportContext,
279                                theOptions,
280                                theCodeSystemUrlAndVersion,
281                                theCode,
282                                theDisplay,
283                                expansion,
284                                vsUrl);
285        }
286
287        @Override
288        @Nullable
289        public CodeValidationResult validateCode(
290                        @Nonnull ValidationSupportContext theValidationSupportContext,
291                        @Nonnull ConceptValidationOptions theOptions,
292                        String theCodeSystem,
293                        String theCode,
294                        String theDisplay,
295                        String theValueSetUrl) {
296                IBaseResource vs;
297                if (isNotBlank(theValueSetUrl)) {
298                        vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl);
299                        if (vs == null) {
300                                return null;
301                        }
302                } else {
303                        String codeSystemUrl;
304                        String codeSystemVersion = null;
305                        int codeSystemVersionIndex = theCodeSystem.indexOf("|");
306                        if (codeSystemVersionIndex > -1) {
307                                codeSystemUrl = theCodeSystem.substring(0, codeSystemVersionIndex);
308                                codeSystemVersion = theCodeSystem.substring(codeSystemVersionIndex + 1);
309                        } else {
310                                codeSystemUrl = theCodeSystem;
311                        }
312                        switch (myCtx.getVersion().getVersion()) {
313                                case DSTU2:
314                                case DSTU2_HL7ORG:
315                                        vs = new org.hl7.fhir.dstu2.model.ValueSet()
316                                                        .setCompose(new org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent()
317                                                                        .addInclude(new org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent()
318                                                                                        .setSystem(theCodeSystem)));
319                                        break;
320                                case DSTU3:
321                                        if (codeSystemVersion != null) {
322                                                vs = new org.hl7.fhir.dstu3.model.ValueSet()
323                                                                .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent()
324                                                                                .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent()
325                                                                                                .setSystem(codeSystemUrl)
326                                                                                                .setVersion(codeSystemVersion)));
327                                        } else {
328                                                vs = new org.hl7.fhir.dstu3.model.ValueSet()
329                                                                .setCompose(new org.hl7.fhir.dstu3.model.ValueSet.ValueSetComposeComponent()
330                                                                                .addInclude(new org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent()
331                                                                                                .setSystem(theCodeSystem)));
332                                        }
333                                        break;
334                                case R4:
335                                        if (codeSystemVersion != null) {
336                                                vs = new org.hl7.fhir.r4.model.ValueSet()
337                                                                .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent()
338                                                                                .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent()
339                                                                                                .setSystem(codeSystemUrl)
340                                                                                                .setVersion(codeSystemVersion)));
341                                        } else {
342                                                vs = new org.hl7.fhir.r4.model.ValueSet()
343                                                                .setCompose(new org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent()
344                                                                                .addInclude(new org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent()
345                                                                                                .setSystem(theCodeSystem)));
346                                        }
347                                        break;
348                                case R4B:
349                                        if (codeSystemVersion != null) {
350                                                vs = new org.hl7.fhir.r4b.model.ValueSet()
351                                                                .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent()
352                                                                                .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent()
353                                                                                                .setSystem(codeSystemUrl)
354                                                                                                .setVersion(codeSystemVersion)));
355                                        } else {
356                                                vs = new org.hl7.fhir.r4b.model.ValueSet()
357                                                                .setCompose(new org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent()
358                                                                                .addInclude(new org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent()
359                                                                                                .setSystem(theCodeSystem)));
360                                        }
361                                        break;
362                                case R5:
363                                        if (codeSystemVersion != null) {
364                                                vs = new org.hl7.fhir.r5.model.ValueSet()
365                                                                .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent()
366                                                                                .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent()
367                                                                                                .setSystem(codeSystemUrl)
368                                                                                                .setVersion(codeSystemVersion)));
369                                        } else {
370                                                vs = new org.hl7.fhir.r5.model.ValueSet()
371                                                                .setCompose(new org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent()
372                                                                                .addInclude(new org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent()
373                                                                                                .setSystem(theCodeSystem)));
374                                        }
375                                        break;
376                                case DSTU2_1:
377                                default:
378                                        throw new IllegalArgumentException(Msg.code(699) + "Can not handle version: "
379                                                        + myCtx.getVersion().getVersion());
380                        }
381                }
382
383                ValueSetExpansionOutcome valueSetExpansionOutcome =
384                                expandValueSet(theValidationSupportContext, vs, theCodeSystem, theCode);
385                if (valueSetExpansionOutcome == null) {
386                        return null;
387                }
388
389                if (valueSetExpansionOutcome.getError() != null) {
390                        return new CodeValidationResult()
391                                        .setSeverity(IssueSeverity.ERROR)
392                                        .setMessage(valueSetExpansionOutcome.getError());
393                }
394
395                IBaseResource expansion = valueSetExpansionOutcome.getValueSet();
396                return validateCodeInExpandedValueSet(
397                                theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl);
398        }
399
400        private CodeValidationResult validateCodeInExpandedValueSet(
401                        ValidationSupportContext theValidationSupportContext,
402                        ConceptValidationOptions theOptions,
403                        String theCodeSystemUrlAndVersionToValidate,
404                        String theCodeToValidate,
405                        String theDisplayToValidate,
406                        IBaseResource theExpansion,
407                        String theValueSetUrl) {
408                assert theExpansion != null;
409
410                boolean caseSensitive = true;
411                IBaseResource codeSystemToValidateResource = null;
412                if (!theOptions.isInferSystem() && isNotBlank(theCodeSystemUrlAndVersionToValidate)) {
413                        codeSystemToValidateResource = theValidationSupportContext
414                                        .getRootValidationSupport()
415                                        .fetchCodeSystem(theCodeSystemUrlAndVersionToValidate);
416                }
417
418                List<FhirVersionIndependentConcept> codes = new ArrayList<>();
419                switch (getFhirVersionEnum(
420                                theValidationSupportContext.getRootValidationSupport().getFhirContext(), theExpansion)) {
421                        case DSTU2: {
422                                ca.uhn.fhir.model.dstu2.resource.ValueSet expansionVs =
423                                                (ca.uhn.fhir.model.dstu2.resource.ValueSet) theExpansion;
424                                List<ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains> contains =
425                                                expansionVs.getExpansion().getContains();
426                                flattenAndConvertCodesDstu2(contains, codes);
427                                break;
428                        }
429                        case DSTU2_HL7ORG: {
430                                ValueSet expansionVs = (ValueSet) theExpansion;
431                                List<ValueSet.ValueSetExpansionContainsComponent> contains =
432                                                expansionVs.getExpansion().getContains();
433                                flattenAndConvertCodesDstu2Hl7Org(contains, codes);
434                                break;
435                        }
436                        case DSTU3: {
437                                org.hl7.fhir.dstu3.model.ValueSet expansionVs = (org.hl7.fhir.dstu3.model.ValueSet) theExpansion;
438                                List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> contains =
439                                                expansionVs.getExpansion().getContains();
440                                flattenAndConvertCodesDstu3(contains, codes);
441                                break;
442                        }
443                        case R4: {
444                                org.hl7.fhir.r4.model.ValueSet expansionVs = (org.hl7.fhir.r4.model.ValueSet) theExpansion;
445                                List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> contains =
446                                                expansionVs.getExpansion().getContains();
447                                flattenAndConvertCodesR4(contains, codes);
448                                break;
449                        }
450                        case R4B: {
451                                org.hl7.fhir.r4b.model.ValueSet expansionVs = (org.hl7.fhir.r4b.model.ValueSet) theExpansion;
452                                List<org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent> contains =
453                                                expansionVs.getExpansion().getContains();
454                                flattenAndConvertCodesR4B(contains, codes);
455                                break;
456                        }
457                        case R5: {
458                                org.hl7.fhir.r5.model.ValueSet expansionVs = (org.hl7.fhir.r5.model.ValueSet) theExpansion;
459                                List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> contains =
460                                                expansionVs.getExpansion().getContains();
461                                flattenAndConvertCodesR5(contains, codes);
462                                break;
463                        }
464                        case DSTU2_1:
465                        default:
466                                throw new IllegalArgumentException(Msg.code(700) + "Can not handle version: "
467                                                + myCtx.getVersion().getVersion());
468                }
469
470                String codeSystemResourceName = null;
471                String codeSystemResourceVersion = null;
472                String codeSystemResourceContentMode = null;
473                if (codeSystemToValidateResource != null) {
474                        switch (getFhirVersionEnum(
475                                        theValidationSupportContext.getRootValidationSupport().getFhirContext(),
476                                        codeSystemToValidateResource)) {
477                                case DSTU2:
478                                case DSTU2_HL7ORG: {
479                                        caseSensitive = true;
480                                        break;
481                                }
482                                case DSTU3: {
483                                        org.hl7.fhir.dstu3.model.CodeSystem systemDstu3 =
484                                                        (org.hl7.fhir.dstu3.model.CodeSystem) codeSystemToValidateResource;
485                                        caseSensitive = systemDstu3.getCaseSensitive();
486                                        codeSystemResourceName = systemDstu3.getName();
487                                        codeSystemResourceVersion = systemDstu3.getVersion();
488                                        codeSystemResourceContentMode =
489                                                        systemDstu3.getContentElement().getValueAsString();
490                                        break;
491                                }
492                                case R4: {
493                                        org.hl7.fhir.r4.model.CodeSystem systemR4 =
494                                                        (org.hl7.fhir.r4.model.CodeSystem) codeSystemToValidateResource;
495                                        caseSensitive = systemR4.getCaseSensitive();
496                                        codeSystemResourceName = systemR4.getName();
497                                        codeSystemResourceVersion = systemR4.getVersion();
498                                        codeSystemResourceContentMode = systemR4.getContentElement().getValueAsString();
499                                        break;
500                                }
501                                case R4B: {
502                                        org.hl7.fhir.r4b.model.CodeSystem systemR4B =
503                                                        (org.hl7.fhir.r4b.model.CodeSystem) codeSystemToValidateResource;
504                                        caseSensitive = systemR4B.getCaseSensitive();
505                                        codeSystemResourceName = systemR4B.getName();
506                                        codeSystemResourceVersion = systemR4B.getVersion();
507                                        codeSystemResourceContentMode =
508                                                        systemR4B.getContentElement().getValueAsString();
509                                        break;
510                                }
511                                case R5: {
512                                        CodeSystem systemR5 = (CodeSystem) codeSystemToValidateResource;
513                                        caseSensitive = systemR5.getCaseSensitive();
514                                        codeSystemResourceName = systemR5.getName();
515                                        codeSystemResourceVersion = systemR5.getVersion();
516                                        codeSystemResourceContentMode = systemR5.getContentElement().getValueAsString();
517                                        break;
518                                }
519                                case DSTU2_1:
520                                default:
521                                        throw new IllegalArgumentException(Msg.code(701) + "Can not handle version: "
522                                                        + myCtx.getVersion().getVersion());
523                        }
524                }
525
526                String codeSystemUrlToValidate = null;
527                String codeSystemVersionToValidate = null;
528                if (theCodeSystemUrlAndVersionToValidate != null) {
529                        int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|");
530                        if (versionIndex > -1) {
531                                codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex);
532                                codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1);
533                        } else {
534                                codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate;
535                        }
536                }
537                for (FhirVersionIndependentConcept nextExpansionCode : codes) {
538
539                        boolean codeMatches;
540                        if (caseSensitive) {
541                                codeMatches = defaultString(theCodeToValidate).equals(nextExpansionCode.getCode());
542                        } else {
543                                codeMatches = defaultString(theCodeToValidate).equalsIgnoreCase(nextExpansionCode.getCode());
544                        }
545                        if (codeMatches) {
546                                if (theOptions.isInferSystem()
547                                                || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate)
548                                                                && (codeSystemVersionToValidate == null
549                                                                                || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) {
550                                        String csVersion = codeSystemResourceVersion;
551                                        if (isNotBlank(nextExpansionCode.getSystemVersion())) {
552                                                csVersion = nextExpansionCode.getSystemVersion();
553                                        }
554                                        if (!theOptions.isValidateDisplay()
555                                                        || (isBlank(nextExpansionCode.getDisplay())
556                                                                        || isBlank(theDisplayToValidate)
557                                                                        || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) {
558                                                CodeValidationResult codeValidationResult = new CodeValidationResult()
559                                                                .setCode(theCodeToValidate)
560                                                                .setDisplay(nextExpansionCode.getDisplay())
561                                                                .setCodeSystemName(codeSystemResourceName)
562                                                                .setCodeSystemVersion(csVersion);
563                                                if (isNotBlank(theValueSetUrl)) {
564                                                        populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult);
565                                                }
566                                                return codeValidationResult;
567                                        } else {
568                                                String messageAppend = "";
569                                                if (isNotBlank(theValueSetUrl)) {
570                                                        messageAppend = " for in-memory expansion of ValueSet: " + theValueSetUrl;
571                                                }
572                                                CodeValidationResult codeValidationResult = createResultForDisplayMismatch(
573                                                                myCtx,
574                                                                theCodeToValidate,
575                                                                theDisplayToValidate,
576                                                                nextExpansionCode.getDisplay(),
577                                                                csVersion,
578                                                                messageAppend,
579                                                                getIssueSeverityForCodeDisplayMismatch());
580                                                if (isNotBlank(theValueSetUrl)) {
581                                                        populateSourceDetailsForInMemoryExpansion(theValueSetUrl, codeValidationResult);
582                                                }
583                                                return codeValidationResult;
584                                        }
585                                }
586                        }
587                }
588
589                ValidationMessage.IssueSeverity severity;
590                String message;
591                if ("fragment".equals(codeSystemResourceContentMode)) {
592                        severity = ValidationMessage.IssueSeverity.WARNING;
593                        message = "Unknown code in fragment CodeSystem '"
594                                        + (isNotBlank(theCodeSystemUrlAndVersionToValidate)
595                                                        ? theCodeSystemUrlAndVersionToValidate + "#"
596                                                        : "")
597                                        + theCodeToValidate + "'";
598                } else {
599                        severity = ValidationMessage.IssueSeverity.ERROR;
600                        message = "Unknown code '"
601                                        + (isNotBlank(theCodeSystemUrlAndVersionToValidate)
602                                                        ? theCodeSystemUrlAndVersionToValidate + "#"
603                                                        : "")
604                                        + theCodeToValidate + "'";
605                }
606                if (isNotBlank(theValueSetUrl)) {
607                        message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'";
608                }
609
610                return new CodeValidationResult().setSeverityCode(severity.toCode()).setMessage(message);
611        }
612
613        @Override
614        public LookupCodeResult lookupCode(
615                        ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) {
616                final String code = theLookupCodeRequest.getCode();
617                final String system = theLookupCodeRequest.getSystem();
618                CodeValidationResult codeValidationResult = validateCode(
619                                theValidationSupportContext,
620                                new ConceptValidationOptions(),
621                                system,
622                                code,
623                                theLookupCodeRequest.getDisplayLanguage(),
624                                null);
625                if (codeValidationResult == null) {
626                        return null;
627                }
628                return codeValidationResult.asLookupCodeResult(system, code);
629        }
630
631        @Nullable
632        private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(
633                        ValidationSupportContext theValidationSupportContext,
634                        ValueSet theInput,
635                        @Nullable String theWantSystemUrlAndVersion,
636                        @Nullable String theWantCode)
637                        throws ExpansionCouldNotBeCompletedInternallyException {
638                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet)
639                                VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false));
640                return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
641        }
642
643        @Nullable
644        private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(
645                        ValidationSupportContext theValidationSupportContext,
646                        ca.uhn.fhir.model.dstu2.resource.ValueSet theInput,
647                        @Nullable String theWantSystemUrlAndVersion,
648                        @Nullable String theWantCode)
649                        throws ExpansionCouldNotBeCompletedInternallyException {
650                IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
651                IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser();
652
653                org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(
654                                org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput));
655                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet)
656                                VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
657                return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
658        }
659
660        @Override
661        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
662                if (isBlank(theSystem)) {
663                        return false;
664                }
665
666                IBaseResource cs =
667                                theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theSystem);
668
669                if (!myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU2_1)) {
670                        return cs != null;
671                }
672
673                if (cs != null) {
674                        IPrimitiveType<?> content =
675                                        getFhirContext().newTerser().getSingleValueOrNull(cs, "content", IPrimitiveType.class);
676                        return !"not-present".equals(content.getValueAsString());
677                }
678
679                return false;
680        }
681
682        @Override
683        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
684                return isNotBlank(theValueSetUrl)
685                                && theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl) != null;
686        }
687
688        private void addCodesDstu2Hl7Org(
689                        List<ValueSet.ConceptDefinitionComponent> theSourceList,
690                        List<CodeSystem.ConceptDefinitionComponent> theTargetList) {
691                for (ValueSet.ConceptDefinitionComponent nextSource : theSourceList) {
692                        CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent()
693                                        .setCode(nextSource.getCode())
694                                        .setDisplay(nextSource.getDisplay());
695                        theTargetList.add(targetConcept);
696                        addCodesDstu2Hl7Org(nextSource.getConcept(), targetConcept.getConcept());
697                }
698        }
699
700        private void addCodesDstu2(
701                        List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList,
702                        List<CodeSystem.ConceptDefinitionComponent> theTargetList) {
703                for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) {
704                        CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent()
705                                        .setCode(nextSource.getCode())
706                                        .setDisplay(nextSource.getDisplay());
707                        theTargetList.add(targetConcept);
708                        addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept());
709                }
710        }
711
712        @Nullable
713        private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(
714                        ValidationSupportContext theValidationSupportContext,
715                        org.hl7.fhir.dstu3.model.ValueSet theInput,
716                        @Nullable String theWantSystemUrlAndVersion,
717                        @Nullable String theWantCode)
718                        throws ExpansionCouldNotBeCompletedInternallyException {
719                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet)
720                                VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false));
721                return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
722        }
723
724        @Nullable
725        private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(
726                        ValidationSupportContext theValidationSupportContext,
727                        org.hl7.fhir.r4.model.ValueSet theInput,
728                        @Nullable String theWantSystemUrlAndVersion,
729                        @Nullable String theWantCode)
730                        throws ExpansionCouldNotBeCompletedInternallyException {
731                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet)
732                                VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false));
733                return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode);
734        }
735
736        @Nullable
737        private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B(
738                        ValidationSupportContext theValidationSupportContext,
739                        org.hl7.fhir.r4b.model.ValueSet theInput,
740                        @Nullable String theWantSystemUrlAndVersion,
741                        @Nullable String theWantCode)
742                        throws ExpansionCouldNotBeCompletedInternallyException {
743                org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet)
744                                VersionConvertorFactory_43_50.convertResource(theInput, new BaseAdvisor_43_50(false));
745                return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode);
746        }
747
748        @Nullable
749        private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(
750                        ValidationSupportContext theValidationSupportContext,
751                        org.hl7.fhir.r5.model.ValueSet theInput,
752                        @Nullable String theWantSystemUrlAndVersion,
753                        @Nullable String theWantCode)
754                        throws ExpansionCouldNotBeCompletedInternallyException {
755                Set<FhirVersionIndependentConcept> concepts = new HashSet<>();
756
757                expandValueSetR5IncludeOrExcludes(
758                                theValidationSupportContext,
759                                concepts,
760                                theInput.getCompose().getInclude(),
761                                true,
762                                theWantSystemUrlAndVersion,
763                                theWantCode);
764                expandValueSetR5IncludeOrExcludes(
765                                theValidationSupportContext,
766                                concepts,
767                                theInput.getCompose().getExclude(),
768                                false,
769                                theWantSystemUrlAndVersion,
770                                theWantCode);
771
772                org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet();
773                for (FhirVersionIndependentConcept next : concepts) {
774                        org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent contains =
775                                        retVal.getExpansion().addContains();
776                        contains.setSystem(next.getSystem());
777                        contains.setCode(next.getCode());
778                        contains.setDisplay(next.getDisplay());
779                        contains.setVersion(next.getSystemVersion());
780                }
781
782                return retVal;
783        }
784
785        /**
786         * Use with caution - this is not a stable API
787         *
788         * @since 5.6.0
789         */
790        public void expandValueSetIncludeOrExclude(
791                        ValidationSupportContext theValidationSupportContext,
792                        Consumer<FhirVersionIndependentConcept> theConsumer,
793                        org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude)
794                        throws ExpansionCouldNotBeCompletedInternallyException {
795                expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude);
796        }
797
798        private void expandValueSetR5IncludeOrExcludes(
799                        ValidationSupportContext theValidationSupportContext,
800                        Set<FhirVersionIndependentConcept> theConcepts,
801                        List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList,
802                        boolean theComposeListIsInclude,
803                        @Nullable String theWantSystemUrlAndVersion,
804                        @Nullable String theWantCode)
805                        throws ExpansionCouldNotBeCompletedInternallyException {
806                Consumer<FhirVersionIndependentConcept> consumer = c -> {
807                        if (theComposeListIsInclude) {
808                                theConcepts.add(c);
809                        } else {
810                                theConcepts.remove(c);
811                        }
812                };
813                expandValueSetR5IncludeOrExcludes(
814                                theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode);
815        }
816
817        private void expandValueSetR5IncludeOrExcludes(
818                        ValidationSupportContext theValidationSupportContext,
819                        Consumer<FhirVersionIndependentConcept> theConsumer,
820                        List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList,
821                        @Nullable String theWantSystemUrlAndVersion,
822                        @Nullable String theWantCode)
823                        throws ExpansionCouldNotBeCompletedInternallyException {
824                ExpansionCouldNotBeCompletedInternallyException caughtException = null;
825                for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) {
826                        try {
827                                boolean outcome = expandValueSetR5IncludeOrExclude(
828                                                theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude);
829                                if (isNotBlank(theWantCode)) {
830                                        if (outcome) {
831                                                return;
832                                        }
833                                }
834                        } catch (ExpansionCouldNotBeCompletedInternallyException e) {
835                                if (isBlank(theWantCode)) {
836                                        throw e;
837                                } else {
838                                        caughtException = e;
839                                }
840                        }
841                }
842                if (caughtException != null) {
843                        throw caughtException;
844                }
845        }
846
847        /**
848         * Returns <code>true</code> if at least one code was added
849         */
850        private boolean expandValueSetR5IncludeOrExclude(
851                        ValidationSupportContext theValidationSupportContext,
852                        Consumer<FhirVersionIndependentConcept> theConsumer,
853                        @Nullable String theWantSystemUrlAndVersion,
854                        @Nullable String theWantCode,
855                        org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude)
856                        throws ExpansionCouldNotBeCompletedInternallyException {
857
858                String wantSystemUrl = null;
859                String wantSystemVersion = null;
860
861                if (theWantSystemUrlAndVersion != null) {
862                        int versionIndex = theWantSystemUrlAndVersion.indexOf(OUR_PIPE_CHARACTER);
863                        if (versionIndex > -1) {
864                                wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex);
865                                wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1);
866                        } else {
867                                wantSystemUrl = theWantSystemUrlAndVersion;
868                        }
869                }
870
871                String includeOrExcludeConceptSystemUrl = theInclude.getSystem();
872                String includeOrExcludeConceptSystemVersion = theInclude.getVersion();
873
874                Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext);
875                Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader =
876                                newValueSetLoader(theValidationSupportContext);
877
878                List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>();
879                CodeSystem includeOrExcludeSystemResource = null;
880
881                if (isNotBlank(includeOrExcludeConceptSystemUrl)) {
882
883                        includeOrExcludeConceptSystemVersion = optionallyPopulateVersionFromUrl(
884                                        includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion);
885                        includeOrExcludeConceptSystemUrl = substringBefore(includeOrExcludeConceptSystemUrl, OUR_PIPE_CHARACTER);
886
887                        if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) {
888                                return false;
889                        }
890
891                        if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) {
892                                return false;
893                        }
894
895                        String loadedCodeSystemUrl;
896                        if (includeOrExcludeConceptSystemVersion != null) {
897                                loadedCodeSystemUrl =
898                                                includeOrExcludeConceptSystemUrl + OUR_PIPE_CHARACTER + includeOrExcludeConceptSystemVersion;
899                        } else {
900                                loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl;
901                        }
902
903                        includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl);
904
905                        boolean isIncludeWithDeclaredConcepts = !theInclude.getConcept().isEmpty();
906
907                        final Set<String> wantCodes;
908                        if (isIncludeWithDeclaredConcepts) {
909                                wantCodes = theInclude.getConcept().stream()
910                                                .map(org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent::getCode)
911                                                .collect(Collectors.toSet());
912                        } else {
913                                wantCodes = null;
914                        }
915
916                        boolean ableToHandleCode = false;
917                        String failureMessage = null;
918                        FailureType failureType = FailureType.OTHER;
919
920                        boolean isIncludeCodeSystemIgnored = includeOrExcludeSystemResource != null
921                                        && includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT;
922
923                        if (includeOrExcludeSystemResource == null || isIncludeCodeSystemIgnored) {
924
925                                if (theWantCode != null) {
926                                        if (theValidationSupportContext
927                                                        .getRootValidationSupport()
928                                                        .isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) {
929                                                LookupCodeResult lookup = theValidationSupportContext
930                                                                .getRootValidationSupport()
931                                                                .lookupCode(
932                                                                                theValidationSupportContext,
933                                                                                new LookupCodeRequest(includeOrExcludeConceptSystemUrl, theWantCode));
934                                                if (lookup != null) {
935                                                        ableToHandleCode = true;
936                                                        if (lookup.isFound()) {
937                                                                CodeSystem.ConceptDefinitionComponent conceptDefinition =
938                                                                                new CodeSystem.ConceptDefinitionComponent()
939                                                                                                .addConcept()
940                                                                                                .setCode(theWantCode)
941                                                                                                .setDisplay(lookup.getCodeDisplay());
942                                                                List<CodeSystem.ConceptDefinitionComponent> codesList =
943                                                                                Collections.singletonList(conceptDefinition);
944                                                                addCodes(
945                                                                                includeOrExcludeConceptSystemUrl,
946                                                                                includeOrExcludeConceptSystemVersion,
947                                                                                codesList,
948                                                                                nextCodeList,
949                                                                                wantCodes);
950                                                        }
951                                                }
952                                        } else {
953
954                                                /*
955                                                 * If we're doing an expansion specifically looking for a single code, that means we're validating that code.
956                                                 * In the case where we have a ValueSet that explicitly enumerates a collection of codes
957                                                 * (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid
958                                                 * even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for
959                                                 * CodeSystems to always be known, but realistically there are always going to be CodeSystems that
960                                                 * can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to
961                                                 * enumerate a set of good codes for them is a nice compromise there.
962                                                 */
963                                                if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) {
964                                                        Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent>
965                                                                        matchingEnumeratedConcept = theInclude.getConcept().stream()
966                                                                                        .filter(t -> Objects.equals(t.getCode(), theWantCode))
967                                                                                        .findFirst();
968
969                                                        // If the ValueSet.compose.include has no individual concepts in it, and
970                                                        // we can't find the actual referenced CodeSystem, we have no choice
971                                                        // but to fail
972                                                        if (isIncludeWithDeclaredConcepts) {
973                                                                ableToHandleCode = true;
974                                                        } else {
975                                                                failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(
976                                                                                includeOrExcludeSystemResource, loadedCodeSystemUrl);
977                                                        }
978
979                                                        if (matchingEnumeratedConcept.isPresent()) {
980                                                                CodeSystem.ConceptDefinitionComponent conceptDefinition =
981                                                                                new CodeSystem.ConceptDefinitionComponent()
982                                                                                                .addConcept()
983                                                                                                .setCode(theWantCode)
984                                                                                                .setDisplay(matchingEnumeratedConcept
985                                                                                                                .get()
986                                                                                                                .getDisplay());
987                                                                List<CodeSystem.ConceptDefinitionComponent> codesList =
988                                                                                Collections.singletonList(conceptDefinition);
989                                                                addCodes(
990                                                                                includeOrExcludeConceptSystemUrl,
991                                                                                includeOrExcludeConceptSystemVersion,
992                                                                                codesList,
993                                                                                nextCodeList,
994                                                                                wantCodes);
995                                                        }
996                                                }
997                                        }
998                                } else {
999                                        boolean isIncludeFromSystem = isNotBlank(theInclude.getSystem())
1000                                                        && theInclude.getValueSet().isEmpty();
1001                                        boolean isIncludeWithFilter = !theInclude.getFilter().isEmpty();
1002                                        if (isIncludeFromSystem && !isIncludeWithFilter) {
1003                                                if (isIncludeWithDeclaredConcepts) {
1004                                                        theInclude.getConcept().stream()
1005                                                                        .map(t -> new FhirVersionIndependentConcept(
1006                                                                                        theInclude.getSystem(),
1007                                                                                        t.getCode(),
1008                                                                                        t.getDisplay(),
1009                                                                                        theInclude.getVersion()))
1010                                                                        .forEach(nextCodeList::add);
1011                                                        ableToHandleCode = true;
1012                                                } else if (isIncludeCodeSystemIgnored) {
1013                                                        ableToHandleCode = true;
1014                                                }
1015                                        }
1016
1017                                        if (!ableToHandleCode) {
1018                                                failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(
1019                                                                includeOrExcludeSystemResource, loadedCodeSystemUrl);
1020                                        }
1021                                }
1022
1023                        } else {
1024                                ableToHandleCode = true;
1025                        }
1026
1027                        if (!ableToHandleCode) {
1028                                if (includeOrExcludeSystemResource == null && failureMessage == null) {
1029                                        failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(
1030                                                        includeOrExcludeSystemResource, loadedCodeSystemUrl);
1031                                }
1032
1033                                if (includeOrExcludeSystemResource == null) {
1034                                        failureType = FailureType.UNKNOWN_CODE_SYSTEM;
1035                                }
1036
1037                                throw new ExpansionCouldNotBeCompletedInternallyException(Msg.code(702) + failureMessage, failureType);
1038                        }
1039
1040                        if (includeOrExcludeSystemResource != null
1041                                        && includeOrExcludeSystemResource.getContent() != Enumerations.CodeSystemContentMode.NOTPRESENT) {
1042                                addCodes(
1043                                                includeOrExcludeConceptSystemUrl,
1044                                                includeOrExcludeConceptSystemVersion,
1045                                                includeOrExcludeSystemResource.getConcept(),
1046                                                nextCodeList,
1047                                                wantCodes);
1048                        }
1049                }
1050
1051                for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) {
1052                        org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString());
1053                        if (vs != null) {
1054                                org.hl7.fhir.r5.model.ValueSet subExpansion =
1055                                                expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode);
1056                                if (subExpansion == null) {
1057                                        throw new ExpansionCouldNotBeCompletedInternallyException(
1058                                                        Msg.code(703) + "Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(),
1059                                                        FailureType.OTHER);
1060                                }
1061                                for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next :
1062                                                subExpansion.getExpansion().getContains()) {
1063                                        nextCodeList.add(new FhirVersionIndependentConcept(
1064                                                        next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
1065                                }
1066                        }
1067                }
1068
1069                boolean retVal = false;
1070
1071                for (FhirVersionIndependentConcept next : nextCodeList) {
1072                        if (includeOrExcludeSystemResource != null && theWantCode != null) {
1073                                boolean matches;
1074                                if (includeOrExcludeSystemResource.getCaseSensitive()) {
1075                                        matches = theWantCode.equals(next.getCode());
1076                                } else {
1077                                        matches = theWantCode.equalsIgnoreCase(next.getCode());
1078                                }
1079                                if (!matches) {
1080                                        continue;
1081                                }
1082                        }
1083
1084                        theConsumer.accept(next);
1085                        retVal = true;
1086                }
1087
1088                return retVal;
1089        }
1090
1091        private Function<String, org.hl7.fhir.r5.model.ValueSet> newValueSetLoader(
1092                        ValidationSupportContext theValidationSupportContext) {
1093                switch (myCtx.getVersion().getVersion()) {
1094                        case DSTU2:
1095                        case DSTU2_HL7ORG:
1096                                return t -> {
1097                                        IBaseResource vs = theValidationSupportContext
1098                                                        .getRootValidationSupport()
1099                                                        .fetchValueSet(t);
1100                                        if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) {
1101                                                IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG)
1102                                                                .newJsonParser();
1103                                                IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser();
1104                                                ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet =
1105                                                                (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs;
1106                                                org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(
1107                                                                org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet));
1108                                                return (org.hl7.fhir.r5.model.ValueSet)
1109                                                                VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
1110                                        } else {
1111                                                org.hl7.fhir.dstu2.model.ValueSet valueSet =
1112                                                                (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext
1113                                                                                .getRootValidationSupport()
1114                                                                                .fetchValueSet(t);
1115                                                return (org.hl7.fhir.r5.model.ValueSet)
1116                                                                VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false));
1117                                        }
1118                                };
1119                        case DSTU3:
1120                                return t -> {
1121                                        org.hl7.fhir.dstu3.model.ValueSet valueSet =
1122                                                        (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext
1123                                                                        .getRootValidationSupport()
1124                                                                        .fetchValueSet(t);
1125                                        return (org.hl7.fhir.r5.model.ValueSet)
1126                                                        VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false));
1127                                };
1128                        case R4:
1129                                return t -> {
1130                                        org.hl7.fhir.r4.model.ValueSet valueSet =
1131                                                        (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext
1132                                                                        .getRootValidationSupport()
1133                                                                        .fetchValueSet(t);
1134                                        return (org.hl7.fhir.r5.model.ValueSet)
1135                                                        VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false));
1136                                };
1137                        case R4B:
1138                                return t -> {
1139                                        org.hl7.fhir.r4b.model.ValueSet valueSet =
1140                                                        (org.hl7.fhir.r4b.model.ValueSet) theValidationSupportContext
1141                                                                        .getRootValidationSupport()
1142                                                                        .fetchValueSet(t);
1143                                        return (org.hl7.fhir.r5.model.ValueSet)
1144                                                        VersionConvertorFactory_43_50.convertResource(valueSet, new BaseAdvisor_43_50(false));
1145                                };
1146                        default:
1147                        case DSTU2_1:
1148                        case R5:
1149                                return t -> (org.hl7.fhir.r5.model.ValueSet)
1150                                                theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
1151                }
1152        }
1153
1154        private Function<String, CodeSystem> newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) {
1155                switch (myCtx.getVersion().getVersion()) {
1156                        case DSTU2:
1157                        case DSTU2_HL7ORG:
1158                                return t -> {
1159                                        IBaseResource codeSystem = theValidationSupportContext
1160                                                        .getRootValidationSupport()
1161                                                        .fetchCodeSystem(t);
1162                                        CodeSystem retVal = null;
1163                                        if (codeSystem != null) {
1164                                                retVal = new CodeSystem();
1165                                                if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) {
1166                                                        ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted =
1167                                                                        (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem;
1168                                                        retVal.setUrl(codeSystemCasted.getUrl());
1169                                                        addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept());
1170                                                } else {
1171                                                        org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted =
1172                                                                        (org.hl7.fhir.dstu2.model.ValueSet) codeSystem;
1173                                                        retVal.setUrl(codeSystemCasted.getUrl());
1174                                                        addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept());
1175                                                }
1176                                        }
1177                                        return retVal;
1178                                };
1179                        case DSTU3:
1180                                return t -> {
1181                                        org.hl7.fhir.dstu3.model.CodeSystem codeSystem =
1182                                                        (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext
1183                                                                        .getRootValidationSupport()
1184                                                                        .fetchCodeSystem(t);
1185                                        return (CodeSystem)
1186                                                        VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false));
1187                                };
1188                        case R4:
1189                                return t -> {
1190                                        org.hl7.fhir.r4.model.CodeSystem codeSystem =
1191                                                        (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext
1192                                                                        .getRootValidationSupport()
1193                                                                        .fetchCodeSystem(t);
1194                                        return (CodeSystem)
1195                                                        VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false));
1196                                };
1197                        case R4B:
1198                                return t -> {
1199                                        org.hl7.fhir.r4b.model.CodeSystem codeSystem =
1200                                                        (org.hl7.fhir.r4b.model.CodeSystem) theValidationSupportContext
1201                                                                        .getRootValidationSupport()
1202                                                                        .fetchCodeSystem(t);
1203                                        return (CodeSystem)
1204                                                        VersionConvertorFactory_43_50.convertResource(codeSystem, new BaseAdvisor_43_50(false));
1205                                };
1206                        case DSTU2_1:
1207                        case R5:
1208                        default:
1209                                return t -> (org.hl7.fhir.r5.model.CodeSystem)
1210                                                theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
1211                }
1212        }
1213
1214        private String getFailureMessageForMissingOrUnusableCodeSystem(
1215                        CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) {
1216                String failureMessage;
1217                if (includeOrExcludeSystemResource == null) {
1218                        failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl;
1219                } else {
1220                        assert includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT;
1221                        failureMessage =
1222                                        "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: "
1223                                                        + loadedCodeSystemUrl;
1224                }
1225                return failureMessage;
1226        }
1227
1228        private void addCodes(
1229                        String theCodeSystemUrl,
1230                        String theCodeSystemVersion,
1231                        List<CodeSystem.ConceptDefinitionComponent> theSource,
1232                        List<FhirVersionIndependentConcept> theTarget,
1233                        Set<String> theCodeFilter) {
1234                for (CodeSystem.ConceptDefinitionComponent next : theSource) {
1235                        if (isNotBlank(next.getCode())) {
1236                                if (theCodeFilter == null || theCodeFilter.contains(next.getCode())) {
1237                                        theTarget.add(new FhirVersionIndependentConcept(
1238                                                        theCodeSystemUrl, next.getCode(), next.getDisplay(), theCodeSystemVersion));
1239                                }
1240                        }
1241                        addCodes(theCodeSystemUrl, theCodeSystemVersion, next.getConcept(), theTarget, theCodeFilter);
1242                }
1243        }
1244
1245        private String optionallyPopulateVersionFromUrl(String theSystemUrl, String theVersion) {
1246                if (contains(theSystemUrl, OUR_PIPE_CHARACTER) && isBlank(theVersion)) {
1247                        theVersion = substringAfter(theSystemUrl, OUR_PIPE_CHARACTER);
1248                }
1249                return theVersion;
1250        }
1251
1252        private static void populateSourceDetailsForInMemoryExpansion(
1253                        String theValueSetUrl, CodeValidationResult codeValidationResult) {
1254                codeValidationResult.setSourceDetails(
1255                                "Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl);
1256        }
1257
1258        public static CodeValidationResult createResultForDisplayMismatch(
1259                        FhirContext theFhirContext,
1260                        String theCode,
1261                        String theDisplay,
1262                        String theExpectedDisplay,
1263                        String theCodeSystemVersion,
1264                        IssueSeverity theIssueSeverityForCodeDisplayMismatch) {
1265                return createResultForDisplayMismatch(
1266                                theFhirContext,
1267                                theCode,
1268                                theDisplay,
1269                                theExpectedDisplay,
1270                                theCodeSystemVersion,
1271                                "",
1272                                theIssueSeverityForCodeDisplayMismatch);
1273        }
1274
1275        private static CodeValidationResult createResultForDisplayMismatch(
1276                        FhirContext theFhirContext,
1277                        String theCode,
1278                        String theDisplay,
1279                        String theExpectedDisplay,
1280                        String theCodeSystemVersion,
1281                        String theMessageAppend,
1282                        IssueSeverity theIssueSeverityForCodeDisplayMismatch) {
1283
1284                String message;
1285                IssueSeverity issueSeverity = theIssueSeverityForCodeDisplayMismatch;
1286                if (issueSeverity == IssueSeverity.INFORMATION) {
1287                        message = null;
1288                        issueSeverity = null;
1289                } else {
1290                        message = theFhirContext
1291                                                        .getLocalizer()
1292                                                        .getMessage(
1293                                                                        InMemoryTerminologyServerValidationSupport.class,
1294                                                                        "displayMismatch",
1295                                                                        theDisplay,
1296                                                                        theExpectedDisplay)
1297                                        + theMessageAppend;
1298                }
1299                return new CodeValidationResult()
1300                                .setSeverity(issueSeverity)
1301                                .setMessage(message)
1302                                .setCode(theCode)
1303                                .setCodeSystemVersion(theCodeSystemVersion)
1304                                .setDisplay(theExpectedDisplay);
1305        }
1306
1307        private static void flattenAndConvertCodesDstu2(
1308                        List<ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains> theInput,
1309                        List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
1310                for (ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains next : theInput) {
1311                        theFhirVersionIndependentConcepts.add(
1312                                        new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay()));
1313                        flattenAndConvertCodesDstu2(next.getContains(), theFhirVersionIndependentConcepts);
1314                }
1315        }
1316
1317        private static void flattenAndConvertCodesDstu2Hl7Org(
1318                        List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput,
1319                        List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
1320                for (org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
1321                        theFhirVersionIndependentConcepts.add(
1322                                        new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay()));
1323                        flattenAndConvertCodesDstu2Hl7Org(next.getContains(), theFhirVersionIndependentConcepts);
1324                }
1325        }
1326
1327        private static void flattenAndConvertCodesDstu3(
1328                        List<org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent> theInput,
1329                        List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
1330                for (org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
1331                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(
1332                                        next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
1333                        flattenAndConvertCodesDstu3(next.getContains(), theFhirVersionIndependentConcepts);
1334                }
1335        }
1336
1337        private static void flattenAndConvertCodesR4(
1338                        List<org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent> theInput,
1339                        List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
1340                for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
1341                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(
1342                                        next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
1343                        flattenAndConvertCodesR4(next.getContains(), theFhirVersionIndependentConcepts);
1344                }
1345        }
1346
1347        private static void flattenAndConvertCodesR4B(
1348                        List<org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent> theInput,
1349                        List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
1350                for (org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
1351                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(
1352                                        next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
1353                        flattenAndConvertCodesR4B(next.getContains(), theFhirVersionIndependentConcepts);
1354                }
1355        }
1356
1357        private static void flattenAndConvertCodesR5(
1358                        List<org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent> theInput,
1359                        List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {
1360                for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : theInput) {
1361                        theFhirVersionIndependentConcepts.add(new FhirVersionIndependentConcept(
1362                                        next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
1363                        flattenAndConvertCodesR5(next.getContains(), theFhirVersionIndependentConcepts);
1364                }
1365        }
1366
1367        public enum FailureType {
1368                UNKNOWN_CODE_SYSTEM,
1369                OTHER
1370        }
1371
1372        public static class ExpansionCouldNotBeCompletedInternallyException extends Exception {
1373
1374                private static final long serialVersionUID = -2226561628771483085L;
1375                private final FailureType myFailureType;
1376
1377                public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) {
1378                        super(theMessage);
1379                        myFailureType = theFailureType;
1380                }
1381
1382                public FailureType getFailureType() {
1383                        return myFailureType;
1384                }
1385        }
1386}