001package org.hl7.fhir.common.hapi.validation.validator;
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.DefaultProfileValidationSupport;
007import ca.uhn.fhir.context.support.IValidationSupport;
008import ca.uhn.fhir.context.support.ValidationSupportContext;
009import ca.uhn.fhir.i18n.Msg;
010import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
011import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
012import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
013import jakarta.annotation.Nonnull;
014import jakarta.annotation.Nullable;
015import org.apache.commons.lang3.StringUtils;
016import org.apache.commons.lang3.Validate;
017import org.apache.commons.lang3.builder.EqualsBuilder;
018import org.apache.commons.lang3.builder.HashCodeBuilder;
019import org.apache.commons.lang3.exception.ExceptionUtils;
020import org.fhir.ucum.UcumService;
021import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
022import org.hl7.fhir.common.hapi.validation.support.ValidationSupportUtils;
023import org.hl7.fhir.exceptions.FHIRException;
024import org.hl7.fhir.exceptions.TerminologyServiceException;
025import org.hl7.fhir.instance.model.api.IBaseResource;
026import org.hl7.fhir.r5.context.IContextResourceLoader;
027import org.hl7.fhir.r5.context.IWorkerContext;
028import org.hl7.fhir.r5.context.IWorkerContextManager;
029import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
030import org.hl7.fhir.r5.model.CodeSystem;
031import org.hl7.fhir.r5.model.CodeableConcept;
032import org.hl7.fhir.r5.model.Coding;
033import org.hl7.fhir.r5.model.ElementDefinition;
034import org.hl7.fhir.r5.model.NamingSystem;
035import org.hl7.fhir.r5.model.OperationOutcome;
036import org.hl7.fhir.r5.model.PackageInformation;
037import org.hl7.fhir.r5.model.Parameters;
038import org.hl7.fhir.r5.model.Resource;
039import org.hl7.fhir.r5.model.StringType;
040import org.hl7.fhir.r5.model.StructureDefinition;
041import org.hl7.fhir.r5.model.ValueSet;
042import org.hl7.fhir.r5.profilemodel.PEBuilder;
043import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
044import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
045import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
046import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
047import org.hl7.fhir.r5.utils.validation.IResourceValidator;
048import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
049import org.hl7.fhir.utilities.FhirPublication;
050import org.hl7.fhir.utilities.TimeTracker;
051import org.hl7.fhir.utilities.i18n.I18nBase;
052import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
053import org.hl7.fhir.utilities.npm.NpmPackage;
054import org.hl7.fhir.utilities.validation.ValidationMessage;
055import org.hl7.fhir.utilities.validation.ValidationOptions;
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059import java.util.ArrayList;
060import java.util.Collections;
061import java.util.Date;
062import java.util.List;
063import java.util.Locale;
064import java.util.Map;
065import java.util.Objects;
066import java.util.Set;
067import java.util.stream.Collectors;
068
069import static ca.uhn.fhir.context.support.IValidationSupport.CodeValidationIssueCoding.INVALID_DISPLAY;
070import static java.util.stream.Collectors.collectingAndThen;
071import static java.util.stream.Collectors.toSet;
072import static org.apache.commons.lang3.StringUtils.isBlank;
073import static org.apache.commons.lang3.StringUtils.isNotBlank;
074
075public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext {
076        private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class);
077
078        /**
079         * When we fetch conformance resources such as StructureDefinitions from {@link IValidationSupport}
080         * they will be returned using whatever version of FHIR the underlying infrastructure is
081         * configured to support. But we need to convert it to R5 since that's what the validator
082         * uses. In order to avoid repeated conversions, we put the converted version of the resource
083         * in the {@link org.hl7.fhir.instance.model.api.IAnyResource#getUserData(String)} map
084         * using this key. Since conformance resources returned by validation support are typically
085         * cached by {@link org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain},
086         * the converted version gets cached too.
087         */
088        private static final String CANONICAL_USERDATA_KEY =
089                        VersionSpecificWorkerContextWrapper.class.getName() + "_CANONICAL_USERDATA_KEY";
090
091        public static final FhirContext FHIR_CONTEXT_R5 = FhirContext.forR5();
092        private final ValidationSupportContext myValidationSupportContext;
093        private final VersionCanonicalizer myVersionCanonicalizer;
094        private volatile List<StructureDefinition> myAllStructures;
095        private volatile Set<String> myAllPrimitiveTypes;
096        private Parameters myExpansionProfile;
097        private volatile FHIRPathEngine myFHIRPathEngine;
098
099        public VersionSpecificWorkerContextWrapper(
100                        ValidationSupportContext theValidationSupportContext, VersionCanonicalizer theVersionCanonicalizer) {
101                myValidationSupportContext = theValidationSupportContext;
102                myVersionCanonicalizer = theVersionCanonicalizer;
103
104                setValidationMessageLanguage(getLocale());
105        }
106
107        @Override
108        public Set<String> getBinaryKeysAsSet() {
109                throw new UnsupportedOperationException(Msg.code(2118));
110        }
111
112        @Override
113        public boolean hasBinaryKey(String s) {
114                return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null;
115        }
116
117        @Override
118        public byte[] getBinaryForKey(String s) {
119                return myValidationSupportContext.getRootValidationSupport().fetchBinary(s);
120        }
121
122        @Override
123        public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException {
124                throw new UnsupportedOperationException(Msg.code(652));
125        }
126
127        @Override
128        public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, List<String> types) throws FHIRException {
129                throw new UnsupportedOperationException(Msg.code(653));
130        }
131
132        @Override
133        public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm)
134                        throws FHIRException {
135                throw new UnsupportedOperationException(Msg.code(654));
136        }
137
138        @Override
139        public boolean hasPackage(String id, String ver) {
140                throw new UnsupportedOperationException(Msg.code(655));
141        }
142
143        @Override
144        public boolean hasPackage(PackageInformation packageInformation) {
145                return false;
146        }
147
148        @Override
149        public PackageInformation getPackage(String id, String ver) {
150                return null;
151        }
152
153        @Override
154        public int getClientRetryCount() {
155                throw new UnsupportedOperationException(Msg.code(656));
156        }
157
158        @Override
159        public IWorkerContext setClientRetryCount(int value) {
160                throw new UnsupportedOperationException(Msg.code(657));
161        }
162
163        @Override
164        public TimeTracker clock() {
165                return null;
166        }
167
168        @Override
169        public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() {
170                throw new UnsupportedOperationException(Msg.code(2235));
171        }
172
173        @Override
174        public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) {
175                throw new UnsupportedOperationException(Msg.code(2266));
176        }
177
178        @Override
179        public String getSpecUrl() {
180                return "";
181        }
182
183        @Override
184        public PEBuilder getProfiledElementBuilder(
185                        PEBuilder.PEElementPropertiesPolicy thePEElementPropertiesPolicy, boolean theB) {
186                throw new UnsupportedOperationException(Msg.code(2264));
187        }
188
189        @Override
190        public PackageInformation getPackageForUrl(String s) {
191                throw new UnsupportedOperationException(Msg.code(2236));
192        }
193
194        @Override
195        public Parameters getExpansionParameters() {
196                return myExpansionProfile;
197        }
198
199        @Override
200        public void setExpansionParameters(Parameters expParameters) {
201                setExpansionProfile(expParameters);
202        }
203
204        public void setExpansionProfile(Parameters expParameters) {
205                myExpansionProfile = expParameters;
206        }
207
208        private List<StructureDefinition> allStructureDefinitions() {
209
210                List<StructureDefinition> retVal = myAllStructures;
211                if (retVal == null) {
212                        List<IBaseResource> allStructureDefinitions =
213                                        myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions();
214                        assert allStructureDefinitions != null;
215
216                        /*
217                         * This method (allStructureDefinitions()) gets called recursively - As we
218                         * try to return all StructureDefinitions, we want to generate snapshots for
219                         * any that don't already have a snapshot. But the snapshot generator in turn
220                         * also calls allStructureDefinitions() - That specific call doesn't require
221                         * that the returned SDs have snapshots generated though.
222                         *
223                         * So, we first just convert to canonical version and store a list containing
224                         * the canonical versions. That way any recursive calls will return the
225                         * stored list. But after that we'll generate all the snapshots and
226                         * store that list instead. If the canonicalization fails with an
227                         * unexpected exception, we wipe the stored list. This is probably an
228                         * unrecoverable failure since this method will probably always
229                         * fail if it fails once. But at least this way we're likley to
230                         * generate useful error messages for the user.
231                         */
232                        retVal = allStructureDefinitions.stream()
233                                        .map(myVersionCanonicalizer::structureDefinitionToCanonical)
234                                        .collect(Collectors.toList());
235                        myAllStructures = retVal;
236
237                        try {
238                                for (IBaseResource next : allStructureDefinitions) {
239                                        Resource converted = convertToCanonicalVersionAndGenerateSnapshot(next, false);
240                                        retVal.add((StructureDefinition) converted);
241                                }
242                                myAllStructures = retVal;
243                        } catch (Exception e) {
244                                ourLog.error("Failure during snapshot generation", e);
245                                myAllStructures = null;
246                        }
247                }
248
249                return retVal;
250        }
251
252        @Override
253        public void cacheResource(Resource res) {}
254
255        @Override
256        public void cacheResourceFromPackage(Resource res, PackageInformation packageDetails) throws FHIRException {}
257
258        @Override
259        public void cachePackage(PackageInformation packageInformation) {}
260
261        @Nonnull
262        private ValidationResult convertValidationResult(
263                        String theSystem, @Nullable IValidationSupport.CodeValidationResult theResult) {
264                ValidationResult retVal = null;
265                if (theResult != null) {
266                        String code = theResult.getCode();
267                        String display = theResult.getDisplay();
268
269                        String issueSeverityCode = theResult.getSeverityCode();
270                        String message = theResult.getMessage();
271                        ValidationMessage.IssueSeverity issueSeverity = null;
272                        if (issueSeverityCode != null) {
273                                issueSeverity = ValidationMessage.IssueSeverity.fromCode(issueSeverityCode);
274                        } else if (isNotBlank(message)) {
275                                issueSeverity = ValidationMessage.IssueSeverity.INFORMATION;
276                        }
277
278                        CodeSystem.ConceptDefinitionComponent conceptDefinitionComponent = null;
279                        if (code != null) {
280                                conceptDefinitionComponent = new CodeSystem.ConceptDefinitionComponent()
281                                                .setCode(code)
282                                                .setDisplay(display);
283                        }
284
285                        retVal = new ValidationResult(
286                                        issueSeverity,
287                                        message,
288                                        theSystem,
289                                        theResult.getCodeSystemVersion(),
290                                        conceptDefinitionComponent,
291                                        display,
292                                        getIssuesForCodeValidation(theResult.getIssues()));
293                }
294
295                if (retVal == null) {
296                        retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed", null);
297                }
298
299                return retVal;
300        }
301
302        private List<OperationOutcome.OperationOutcomeIssueComponent> getIssuesForCodeValidation(
303                        List<IValidationSupport.CodeValidationIssue> theIssues) {
304                List<OperationOutcome.OperationOutcomeIssueComponent> issueComponents = new ArrayList<>();
305
306                for (IValidationSupport.CodeValidationIssue issue : theIssues) {
307                        OperationOutcome.IssueSeverity severity =
308                                        OperationOutcome.IssueSeverity.fromCode(issue.getSeverity().getCode());
309                        OperationOutcome.IssueType issueType =
310                                        OperationOutcome.IssueType.fromCode(issue.getType().getCode());
311                        String diagnostics = issue.getDiagnostics();
312
313                        IValidationSupport.CodeValidationIssueDetails details = issue.getDetails();
314                        CodeableConcept codeableConcept = new CodeableConcept().setText(details.getText());
315                        details.getCodings().forEach(detailCoding -> codeableConcept
316                                        .addCoding()
317                                        .setSystem(detailCoding.getSystem())
318                                        .setCode(detailCoding.getCode()));
319
320                        OperationOutcome.OperationOutcomeIssueComponent issueComponent =
321                                        new OperationOutcome.OperationOutcomeIssueComponent()
322                                                        .setSeverity(severity)
323                                                        .setCode(issueType)
324                                                        .setDetails(codeableConcept)
325                                                        .setDiagnostics(diagnostics);
326                        issueComponent
327                                        .addExtension()
328                                        .setUrl("http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id")
329                                        .setValue(new StringType("Terminology_PassThrough_TX_Message"));
330                        issueComponents.add(issueComponent);
331                }
332                return issueComponents;
333        }
334
335        @Override
336        public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean Hierarchical) {
337                IBaseResource convertedSource;
338                try {
339                        convertedSource = myVersionCanonicalizer.valueSetFromValidatorCanonical(source);
340                } catch (FHIRException e) {
341                        throw new InternalErrorException(Msg.code(661) + e);
342                }
343                IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext
344                                .getRootValidationSupport()
345                                .expandValueSet(myValidationSupportContext, null, convertedSource);
346
347                ValueSet convertedResult = null;
348                if (expanded.getValueSet() != null) {
349                        try {
350                                convertedResult = myVersionCanonicalizer.valueSetToValidatorCanonical(expanded.getValueSet());
351                        } catch (FHIRException e) {
352                                throw new InternalErrorException(Msg.code(662) + e);
353                        }
354                }
355
356                String error = expanded.getError();
357                TerminologyServiceErrorClass result = null;
358
359                return new ValueSetExpansionOutcome(convertedResult, error, result, expanded.getErrorIsFromServer());
360        }
361
362        @Override
363        public ValueSetExpansionOutcome expandVS(
364                        Resource src,
365                        ElementDefinition.ElementDefinitionBindingComponent binding,
366                        boolean cacheOk,
367                        boolean Hierarchical) {
368                ValueSet valueSet = fetchResource(ValueSet.class, binding.getValueSet(), src);
369                return expandVS(valueSet, cacheOk, Hierarchical);
370        }
371
372        @Override
373        public ValueSetExpansionOutcome expandVS(ValueSet.ConceptSetComponent inc, boolean hierarchical, boolean noInactive)
374                        throws TerminologyServiceException {
375                throw new UnsupportedOperationException(Msg.code(664));
376        }
377
378        @Override
379        public Locale getLocale() {
380                return myValidationSupportContext
381                                .getRootValidationSupport()
382                                .getFhirContext()
383                                .getLocalizer()
384                                .getLocale();
385        }
386
387        @Override
388        public void setLocale(Locale locale) {
389                // ignore
390        }
391
392        @Override
393        public CodeSystem fetchCodeSystem(String system) {
394                IBaseResource fetched =
395                                myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system);
396                if (fetched == null) {
397                        return null;
398                }
399                try {
400                        return myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched);
401                } catch (FHIRException e) {
402                        throw new InternalErrorException(Msg.code(665) + e);
403                }
404        }
405
406        @Override
407        public CodeSystem fetchCodeSystem(String system, String verison) {
408                IBaseResource fetched =
409                                myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system);
410                if (fetched == null) {
411                        return null;
412                }
413                try {
414                        return myVersionCanonicalizer.codeSystemToValidatorCanonical(fetched);
415                } catch (FHIRException e) {
416                        throw new InternalErrorException(Msg.code(1992) + e);
417                }
418        }
419
420        @Override
421        public CodeSystem fetchCodeSystem(String system, FhirPublication fhirVersion) {
422                return null;
423        }
424
425        @Override
426        public CodeSystem fetchCodeSystem(String system, String version, FhirPublication fhirVersion) {
427                return null;
428        }
429
430        @Override
431        public CodeSystem fetchSupplementedCodeSystem(String system) {
432                return null;
433        }
434
435        @Override
436        public CodeSystem fetchSupplementedCodeSystem(String system, String version) {
437                return null;
438        }
439
440        @Override
441        public CodeSystem fetchSupplementedCodeSystem(String system, FhirPublication fhirVersion) {
442                return null;
443        }
444
445        @Override
446        public CodeSystem fetchSupplementedCodeSystem(String system, String version, FhirPublication fhirVersion) {
447                return null;
448        }
449
450        @Override
451        public <T extends Resource> T fetchResourceRaw(Class<T> class_, String uri) {
452                return fetchResource(class_, uri);
453        }
454
455        @Override
456        public <T extends Resource> T fetchResource(Class<T> class_, String theUri) {
457                if (isBlank(theUri)) {
458                        return null;
459                }
460
461                if (StringUtils.countMatches(theUri, "|") > 1) {
462                        ourLog.warn("Unrecognized profile uri: {}", theUri);
463                }
464
465                String resourceType = getResourceType(class_);
466                @SuppressWarnings("unchecked")
467                T retVal = (T) fetchResource(resourceType, theUri);
468
469                return retVal;
470        }
471
472        @Override
473        public Resource fetchResourceById(String type, String uri) {
474                throw new UnsupportedOperationException(Msg.code(666));
475        }
476
477        @Override
478        public Resource fetchResourceById(String type, String uri, FhirPublication fhirVersion) {
479                return null;
480        }
481
482        @Override
483        public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
484                T retVal = fetchResource(class_, uri);
485                if (retVal == null) {
486                        throw new FHIRException(
487                                        Msg.code(667) + "Can not find resource of type " + class_.getSimpleName() + " with uri " + uri);
488                }
489                return retVal;
490        }
491
492        @Override
493        public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
494                return fetchResource(class_, uri + "|" + version);
495        }
496
497        @Override
498        public <T extends Resource> T fetchResource(Class<T> class_, String uri, FhirPublication fhirVersion) {
499                return null;
500        }
501
502        @Override
503        public <T extends Resource> T fetchResource(
504                        Class<T> class_, String uri, String version, FhirPublication fhirVersion) {
505                return null;
506        }
507
508        @Override
509        public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource canonicalForSource) {
510                return fetchResource(class_, uri);
511        }
512
513        @Override
514        public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_, FhirPublication fhirVersion) {
515                return null;
516        }
517
518        @Override
519        public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, Resource sourceOfReference)
520                        throws FHIRException {
521                throw new UnsupportedOperationException(Msg.code(2214));
522        }
523
524        @Override
525        public List<String> getResourceNames() {
526                return new ArrayList<>(myValidationSupportContext
527                                .getRootValidationSupport()
528                                .getFhirContext()
529                                .getResourceTypes());
530        }
531
532        @Override
533        public List<String> getResourceNames(FhirPublication fhirVersion) {
534                return null;
535        }
536
537        @Override
538        public Set<String> getResourceNamesAsSet() {
539                return myValidationSupportContext
540                                .getRootValidationSupport()
541                                .getFhirContext()
542                                .getResourceTypes();
543        }
544
545        @Override
546        public Set<String> getResourceNamesAsSet(FhirPublication theFhirVersion) {
547                return null;
548        }
549
550        @Override
551        public StructureDefinition fetchTypeDefinition(String theTypeName) {
552                return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + theTypeName);
553        }
554
555        @Override
556        public StructureDefinition fetchTypeDefinition(String theTypeName, FhirPublication theFhirVersion) {
557                return null;
558        }
559
560        @Override
561        public List<StructureDefinition> fetchTypeDefinitions(String theTypeName) {
562                List<StructureDefinition> allStructures = new ArrayList<>(allStructureDefinitions());
563                allStructures.removeIf(sd -> !sd.hasType() || !sd.getType().equals(theTypeName));
564                return allStructures;
565        }
566
567        @Override
568        public List<StructureDefinition> fetchTypeDefinitions(String theTypeName, FhirPublication theFhirVersion) {
569                return null;
570        }
571
572        @Override
573        public boolean isPrimitiveType(String theType) {
574                return allPrimitiveTypes().contains(theType);
575        }
576
577        private Set<String> allPrimitiveTypes() {
578                Set<String> retVal = myAllPrimitiveTypes;
579                if (retVal == null) {
580                        // Collector may be changed to Collectors.toUnmodifiableSet() when switching to Android API level >= 33
581                        retVal = allStructureDefinitions().stream()
582                                        .filter(structureDefinition ->
583                                                        structureDefinition.getKind() == StructureDefinition.StructureDefinitionKind.PRIMITIVETYPE)
584                                        .map(StructureDefinition::getName)
585                                        .filter(Objects::nonNull)
586                                        .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
587                        myAllPrimitiveTypes = retVal;
588                }
589
590                return retVal;
591        }
592
593        @Override
594        public boolean isDataType(String theType) {
595                return !isPrimitiveType(theType);
596        }
597
598        @Override
599        public UcumService getUcumService() {
600                throw new UnsupportedOperationException(Msg.code(676));
601        }
602
603        @Override
604        public void setUcumService(UcumService ucumService) {
605                throw new UnsupportedOperationException(Msg.code(677));
606        }
607
608        @Override
609        public String getVersion() {
610                return myValidationSupportContext
611                                .getRootValidationSupport()
612                                .getFhirContext()
613                                .getVersion()
614                                .getVersion()
615                                .getFhirVersionString();
616        }
617
618        @Override
619        public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
620                if (isBlank(uri)) {
621                        return false;
622                }
623
624                String resourceType = getResourceType(class_);
625                return fetchResource(resourceType, uri) != null;
626        }
627
628        private static <T extends Resource> String getResourceType(Class<T> theClass) {
629                if (theClass.getSimpleName().equals("Resource")) {
630                        return "Resource";
631                }
632                return FHIR_CONTEXT_R5.getResourceType(theClass);
633        }
634
635        @Override
636        public <T extends Resource> boolean hasResource(Class<T> class_, String uri, Resource sourceOfReference) {
637                return false;
638        }
639
640        @Override
641        public <T extends Resource> boolean hasResource(Class<T> class_, String uri, FhirPublication fhirVersion) {
642                return false;
643        }
644
645        @Override
646        public boolean isNoTerminologyServer() {
647                return false;
648        }
649
650        @Override
651        public Set<String> getCodeSystemsUsed() {
652                throw new UnsupportedOperationException(Msg.code(681));
653        }
654
655        @Override
656        public IResourceValidator newValidator() {
657                throw new UnsupportedOperationException(Msg.code(684));
658        }
659
660        @Override
661        public Map<String, NamingSystem> getNSUrlMap() {
662                throw new UnsupportedOperationException(Msg.code(2265));
663        }
664
665        @Override
666        public org.hl7.fhir.r5.context.ILoggingService getLogger() {
667                return null;
668        }
669
670        @Override
671        public void setLogger(org.hl7.fhir.r5.context.ILoggingService logger) {
672                throw new UnsupportedOperationException(Msg.code(687));
673        }
674
675        @Override
676        public boolean supportsSystem(String system) {
677                return myValidationSupportContext
678                                .getRootValidationSupport()
679                                .isCodeSystemSupported(myValidationSupportContext, system);
680        }
681
682        @Override
683        public boolean supportsSystem(String system, FhirPublication fhirVersion) throws TerminologyServiceException {
684                return supportsSystem(system);
685        }
686
687        @Override
688        public ValueSetExpansionOutcome expandVS(
689                        ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk) {
690                return null;
691        }
692
693        @Override
694        public ValidationResult validateCode(
695                        ValidationOptions theOptions, String system, String version, String code, String display) {
696                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions);
697                return doValidation(null, validationOptions, system, code, display);
698        }
699
700        @Override
701        public ValidationResult validateCode(
702                        ValidationOptions theOptions,
703                        String theSystem,
704                        String version,
705                        String theCode,
706                        String display,
707                        ValueSet theValueSet) {
708                IBaseResource convertedVs = null;
709
710                try {
711                        if (theValueSet != null) {
712                                convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet);
713                        }
714                } catch (FHIRException e) {
715                        throw new InternalErrorException(Msg.code(689) + e);
716                }
717
718                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions);
719
720                return doValidation(convertedVs, validationOptions, theSystem, theCode, display);
721        }
722
723        @Override
724        public ValidationResult validateCode(ValidationOptions theOptions, String code, ValueSet theValueSet) {
725                IBaseResource convertedVs = null;
726                try {
727                        if (theValueSet != null) {
728                                convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet);
729                        }
730                } catch (FHIRException e) {
731                        throw new InternalErrorException(Msg.code(690) + e);
732                }
733
734                String system = ValidationSupportUtils.extractCodeSystemForCode(theValueSet, code);
735
736                ConceptValidationOptions validationOptions =
737                                convertConceptValidationOptions(theOptions).setInferSystem(true);
738
739                return doValidation(convertedVs, validationOptions, system, code, null);
740        }
741
742        @Override
743        public ValidationResult validateCode(ValidationOptions theOptions, Coding theCoding, ValueSet theValueSet) {
744                IBaseResource convertedVs = null;
745
746                try {
747                        if (theValueSet != null) {
748                                convertedVs = myVersionCanonicalizer.valueSetFromValidatorCanonical(theValueSet);
749                        }
750                } catch (FHIRException e) {
751                        throw new InternalErrorException(Msg.code(691) + e);
752                }
753
754                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions);
755                String system = theCoding.getSystem();
756                String code = theCoding.getCode();
757                String display = theCoding.getDisplay();
758
759                return doValidation(convertedVs, validationOptions, system, code, display);
760        }
761
762        @Override
763        public ValidationResult validateCode(
764                        ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) {
765                return validateCode(options, code, vs);
766        }
767
768        @Override
769        public void validateCodeBatch(
770                        ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
771                for (CodingValidationRequest next : codes) {
772                        ValidationResult outcome = validateCode(options, next.getCoding(), vs);
773                        next.setResult(outcome);
774                }
775        }
776
777        @Override
778        public void validateCodeBatchByRef(
779                        ValidationOptions validationOptions, List<? extends CodingValidationRequest> list, String s) {
780                ValueSet valueSet = fetchResource(ValueSet.class, s);
781                validateCodeBatch(validationOptions, list, valueSet);
782        }
783
784        @Nonnull
785        private ValidationResult doValidation(
786                        IBaseResource theValueSet,
787                        ConceptValidationOptions theValidationOptions,
788                        String theSystem,
789                        String theCode,
790                        String theDisplay) {
791                IValidationSupport.CodeValidationResult result;
792                if (theValueSet != null) {
793                        result = validateCodeInValueSet(theValueSet, theValidationOptions, theSystem, theCode, theDisplay);
794                } else {
795                        result = validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay);
796                }
797                return convertValidationResult(theSystem, result);
798        }
799
800        private IValidationSupport.CodeValidationResult validateCodeInValueSet(
801                        IBaseResource theValueSet,
802                        ConceptValidationOptions theValidationOptions,
803                        String theSystem,
804                        String theCode,
805                        String theDisplay) {
806                IValidationSupport.CodeValidationResult result = myValidationSupportContext
807                                .getRootValidationSupport()
808                                .validateCodeInValueSet(
809                                                myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet);
810                if (result != null && isNotBlank(theSystem)) {
811                        /* We got a value set result, which could be successful, or could contain errors/warnings. The code
812                        might also be invalid in the code system, so we will check that as well and add those issues
813                        to our result.
814                        */
815                        IValidationSupport.CodeValidationResult codeSystemResult =
816                                        validateCodeInCodeSystem(theValidationOptions, theSystem, theCode, theDisplay);
817                        final boolean valueSetResultContainsInvalidDisplay = result.getIssues().stream()
818                                        .anyMatch(VersionSpecificWorkerContextWrapper::hasInvalidDisplayDetailCode);
819                        if (codeSystemResult != null) {
820                                for (IValidationSupport.CodeValidationIssue codeValidationIssue : codeSystemResult.getIssues()) {
821                                        /* Value set validation should already have checked the display name. If we get INVALID_DISPLAY
822                                        issues from code system validation, they will only repeat what was already caught.
823                                        */
824                                        if (!hasInvalidDisplayDetailCode(codeValidationIssue) || !valueSetResultContainsInvalidDisplay) {
825                                                result.addIssue(codeValidationIssue);
826                                        }
827                                }
828                        }
829                }
830                return result;
831        }
832
833        private static boolean hasInvalidDisplayDetailCode(IValidationSupport.CodeValidationIssue theIssue) {
834                return theIssue.hasIssueDetailCode(INVALID_DISPLAY.getCode());
835        }
836
837        private IValidationSupport.CodeValidationResult validateCodeInCodeSystem(
838                        ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) {
839                return myValidationSupportContext
840                                .getRootValidationSupport()
841                                .validateCode(myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null);
842        }
843
844        @Override
845        public ValidationResult validateCode(ValidationOptions theOptions, CodeableConcept code, ValueSet theVs) {
846
847                List<ValidationResult> validationResultsOk = new ArrayList<>();
848                List<OperationOutcome.OperationOutcomeIssueComponent> issues = new ArrayList<>();
849                for (Coding next : code.getCoding()) {
850                        if (!next.hasSystem()) {
851                                String message =
852                                                "Coding has no system. A code with no system has no defined meaning, and it cannot be validated. A system should be provided";
853                                OperationOutcome.OperationOutcomeIssueComponent issue =
854                                                new OperationOutcome.OperationOutcomeIssueComponent()
855                                                                .setSeverity(OperationOutcome.IssueSeverity.WARNING)
856                                                                .setCode(OperationOutcome.IssueType.NOTFOUND)
857                                                                .setDiagnostics(message)
858                                                                .setDetails(new CodeableConcept().setText(message));
859
860                                issues.add(issue);
861                        }
862                        ValidationResult retVal = validateCode(theOptions, next, theVs);
863                        if (retVal.isOk()) {
864                                validationResultsOk.add(retVal);
865                        } else {
866                                for (OperationOutcome.OperationOutcomeIssueComponent issue : retVal.getIssues()) {
867                                        issues.add(issue);
868                                }
869                        }
870                }
871
872                if (code.getCoding().size() > 0) {
873                        if (!myValidationSupportContext.isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid()) {
874                                if (validationResultsOk.size() == code.getCoding().size()) {
875                                        return validationResultsOk.get(0);
876                                }
877                        } else {
878                                if (validationResultsOk.size() > 0) {
879                                        return validationResultsOk.get(0);
880                                }
881                        }
882                }
883
884                return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null, issues);
885        }
886
887        private static OperationOutcome.OperationOutcomeIssueComponent getOperationOutcomeTxIssueComponent(
888                        String message, OperationOutcome.IssueType issueCode, String txIssueTypeCode) {
889                OperationOutcome.OperationOutcomeIssueComponent issue = new OperationOutcome.OperationOutcomeIssueComponent()
890                                .setSeverity(OperationOutcome.IssueSeverity.ERROR)
891                                .setDiagnostics(message);
892                issue.getDetails().setText(message);
893
894                issue.setCode(issueCode);
895                issue.getDetails().addCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", txIssueTypeCode, null);
896                return issue;
897        }
898
899        public void invalidateCaches() {
900                // nothing for now
901        }
902
903        @Override
904        public <T extends Resource> List<T> fetchResourcesByType(Class<T> theClass) {
905                if (theClass.equals(StructureDefinition.class)) {
906                        return (List<T>) allStructureDefinitions();
907                }
908                throw new UnsupportedOperationException(Msg.code(650) + "Unable to fetch resources of type: " + theClass);
909        }
910
911        @Override
912        public <T extends Resource> List<T> fetchResourcesByUrl(Class<T> class_, String url) {
913                throw new UnsupportedOperationException(Msg.code(2509) + "Can't fetch all resources of url: " + url);
914        }
915
916        @Override
917        public boolean isForPublication() {
918                return false;
919        }
920
921        @Override
922        public void setForPublication(boolean b) {
923                throw new UnsupportedOperationException(Msg.code(2351));
924        }
925
926        @Override
927        public OIDSummary urlsForOid(String oid, String resourceType) {
928                return null;
929        }
930
931        @Override
932        public <T extends Resource> T findTxResource(Class<T> class_, String canonical, Resource sourceOfReference) {
933                if (canonical == null) {
934                        return null;
935                }
936                return fetchResource(class_, canonical, sourceOfReference);
937        }
938
939        @Override
940        public <T extends Resource> T findTxResource(Class<T> class_, String canonical) {
941                if (canonical == null) {
942                        return null;
943                }
944
945                return fetchResource(class_, canonical);
946        }
947
948        @Override
949        public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version) {
950                if (canonical == null) {
951                        return null;
952                }
953
954                return fetchResource(class_, canonical, version);
955        }
956
957        public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) {
958                ConceptValidationOptions retVal = new ConceptValidationOptions();
959                if (theOptions.isGuessSystem()) {
960                        retVal = retVal.setInferSystem(true);
961                }
962                return retVal;
963        }
964
965        @Nonnull
966        public static VersionSpecificWorkerContextWrapper newVersionSpecificWorkerContextWrapper(
967                        IValidationSupport theValidationSupport) {
968                VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(theValidationSupport.getFhirContext());
969                return new VersionSpecificWorkerContextWrapper(
970                                new ValidationSupportContext(theValidationSupport), versionCanonicalizer);
971        }
972
973        private static class ResourceKey {
974                private final int myHashCode;
975                private final String myResourceName;
976                private final String myUri;
977
978                private ResourceKey(String theResourceName, String theUri) {
979                        myResourceName = theResourceName;
980                        myUri = theUri;
981                        myHashCode = new HashCodeBuilder(17, 37)
982                                        .append(myResourceName)
983                                        .append(myUri)
984                                        .toHashCode();
985                }
986
987                @Override
988                public boolean equals(Object theO) {
989                        if (this == theO) {
990                                return true;
991                        }
992
993                        if (theO == null || getClass() != theO.getClass()) {
994                                return false;
995                        }
996
997                        ResourceKey that = (ResourceKey) theO;
998
999                        return new EqualsBuilder()
1000                                        .append(myResourceName, that.myResourceName)
1001                                        .append(myUri, that.myUri)
1002                                        .isEquals();
1003                }
1004
1005                public String getResourceName() {
1006                        return myResourceName;
1007                }
1008
1009                public String getUri() {
1010                        return myUri;
1011                }
1012
1013                @Override
1014                public int hashCode() {
1015                        return myHashCode;
1016                }
1017        }
1018
1019        @Override
1020        public Boolean subsumes(ValidationOptions optionsArg, Coding parent, Coding child) {
1021                throw new UnsupportedOperationException(Msg.code(2489));
1022        }
1023
1024        @Override
1025        public boolean isServerSideSystem(String url) {
1026                return false;
1027        }
1028
1029        private IBaseResource fetchResource(String theResourceType, String theUrl) {
1030                String fetchResourceName = theResourceType;
1031                if (myValidationSupportContext
1032                                                .getRootValidationSupport()
1033                                                .getFhirContext()
1034                                                .getVersion()
1035                                                .getVersion()
1036                                == FhirVersionEnum.DSTU2) {
1037                        if ("CodeSystem".equals(fetchResourceName)) {
1038                                fetchResourceName = "ValueSet";
1039                        }
1040                }
1041
1042                Class<? extends IBaseResource> fetchResourceType;
1043                if (fetchResourceName.equals("Resource")) {
1044                        fetchResourceType = null;
1045                } else {
1046                        fetchResourceType = myValidationSupportContext
1047                                        .getRootValidationSupport()
1048                                        .getFhirContext()
1049                                        .getResourceDefinition(fetchResourceName)
1050                                        .getImplementingClass();
1051                }
1052
1053                IBaseResource fetched =
1054                                myValidationSupportContext.getRootValidationSupport().fetchResource(fetchResourceType, theUrl);
1055
1056                if (fetched == null) {
1057                        return null;
1058                }
1059
1060                return convertToCanonicalVersionAndGenerateSnapshot(fetched, true);
1061        }
1062
1063        private Resource convertToCanonicalVersionAndGenerateSnapshot(
1064                        @Nonnull IBaseResource theResource, boolean thePropagateSnapshotException) {
1065                Resource canonical;
1066                synchronized (theResource) {
1067                        canonical = (Resource) theResource.getUserData(CANONICAL_USERDATA_KEY);
1068                        if (canonical == null) {
1069                                boolean storeCanonical = true;
1070                                canonical = myVersionCanonicalizer.resourceToValidatorCanonical(theResource);
1071
1072                                if (canonical instanceof StructureDefinition) {
1073                                        StructureDefinition canonicalSd = (StructureDefinition) canonical;
1074                                        if (canonicalSd.getSnapshot().isEmpty()) {
1075                                                ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl());
1076                                                IBaseResource resource = theResource;
1077                                                try {
1078
1079                                                        FhirContext fhirContext = myValidationSupportContext
1080                                                                        .getRootValidationSupport()
1081                                                                        .getFhirContext();
1082                                                        SnapshotGeneratingValidationSupport snapshotGenerator =
1083                                                                        new SnapshotGeneratingValidationSupport(fhirContext, this, getFHIRPathEngine());
1084                                                        resource = snapshotGenerator.generateSnapshot(
1085                                                                        myValidationSupportContext, resource, "", null, "");
1086                                                        Validate.isTrue(
1087                                                                        resource != null,
1088                                                                        "StructureDefinition %s has no snapshot, and no snapshot generator is configured",
1089                                                                        canonicalSd.getUrl());
1090
1091                                                } catch (BaseServerResponseException e) {
1092                                                        if (thePropagateSnapshotException) {
1093                                                                throw e;
1094                                                        }
1095                                                        String message = e.toString();
1096                                                        Throwable rootCause = ExceptionUtils.getRootCause(e);
1097                                                        if (rootCause != null) {
1098                                                                message = rootCause.getMessage();
1099                                                        }
1100                                                        ourLog.warn(
1101                                                                        "Failed to generate snapshot for profile with URL[{}]: {}",
1102                                                                        canonicalSd.getUrl(),
1103                                                                        message);
1104                                                        storeCanonical = false;
1105                                                }
1106
1107                                                canonical = myVersionCanonicalizer.resourceToValidatorCanonical(resource);
1108                                        }
1109                                }
1110
1111                                String sourcePackageId =
1112                                                (String) theResource.getUserData(DefaultProfileValidationSupport.SOURCE_PACKAGE_ID);
1113                                if (sourcePackageId != null) {
1114                                        canonical.setSourcePackage(new PackageInformation(sourcePackageId, null, null, new Date()));
1115                                }
1116
1117                                if (storeCanonical) {
1118                                        theResource.setUserData(CANONICAL_USERDATA_KEY, canonical);
1119                                }
1120                        }
1121                }
1122                return canonical;
1123        }
1124
1125        private FHIRPathEngine getFHIRPathEngine() {
1126                FHIRPathEngine retVal = myFHIRPathEngine;
1127                if (retVal == null) {
1128                        retVal = new FHIRPathEngine(this);
1129                        myFHIRPathEngine = retVal;
1130                }
1131                return retVal;
1132        }
1133}