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