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.IValidationSupport;
007import ca.uhn.fhir.context.support.ValidationSupportContext;
008import ca.uhn.fhir.rest.api.Constants;
009import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
010import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
011import com.github.benmanes.caffeine.cache.Caffeine;
012import com.github.benmanes.caffeine.cache.LoadingCache;
013import org.apache.commons.lang3.Validate;
014import org.apache.commons.lang3.builder.EqualsBuilder;
015import org.apache.commons.lang3.builder.HashCodeBuilder;
016import org.apache.commons.lang3.time.DateUtils;
017import org.fhir.ucum.UcumService;
018import org.hl7.fhir.exceptions.FHIRException;
019import org.hl7.fhir.exceptions.TerminologyServiceException;
020import org.hl7.fhir.instance.model.api.IBaseResource;
021import org.hl7.fhir.r5.context.IWorkerContext;
022import org.hl7.fhir.r5.formats.IParser;
023import org.hl7.fhir.r5.formats.ParserType;
024import org.hl7.fhir.r5.model.CanonicalResource;
025import org.hl7.fhir.r5.model.Coding;
026import org.hl7.fhir.r5.model.Resource;
027import org.hl7.fhir.r5.model.StructureDefinition;
028import org.hl7.fhir.r5.model.ValueSet;
029import org.hl7.fhir.r5.terminologies.ValueSetExpander;
030import org.hl7.fhir.r5.utils.IResourceValidator;
031import org.hl7.fhir.utilities.TimeTracker;
032import org.hl7.fhir.utilities.TranslationServices;
033import org.hl7.fhir.utilities.i18n.I18nBase;
034import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
035import org.hl7.fhir.utilities.npm.NpmPackage;
036import org.hl7.fhir.utilities.validation.ValidationMessage;
037import org.hl7.fhir.utilities.validation.ValidationOptions;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import javax.annotation.Nonnull;
042import javax.annotation.Nullable;
043import java.util.ArrayList;
044import java.util.List;
045import java.util.Locale;
046import java.util.Map;
047import java.util.Set;
048import java.util.concurrent.TimeUnit;
049
050import static org.apache.commons.lang3.StringUtils.isBlank;
051import static org.apache.commons.lang3.StringUtils.isNotBlank;
052
053public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWorkerContext {
054        public static final IVersionTypeConverter IDENTITY_VERSION_TYPE_CONVERTER = new VersionTypeConverterR5();
055        private static final Logger ourLog = LoggerFactory.getLogger(VersionSpecificWorkerContextWrapper.class);
056        private static final FhirContext ourR5Context = FhirContext.forR5();
057        private final ValidationSupportContext myValidationSupportContext;
058        private final IVersionTypeConverter myModelConverter;
059        private final LoadingCache<ResourceKey, IBaseResource> myFetchResourceCache;
060        private volatile List<StructureDefinition> myAllStructures;
061        private org.hl7.fhir.r5.model.Parameters myExpansionProfile;
062
063        public VersionSpecificWorkerContextWrapper(ValidationSupportContext theValidationSupportContext, IVersionTypeConverter theModelConverter) {
064                myValidationSupportContext = theValidationSupportContext;
065                myModelConverter = theModelConverter;
066
067                long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND;
068                if (System.getProperties().containsKey(ca.uhn.fhir.rest.api.Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) {
069                        timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
070                }
071
072                myFetchResourceCache = Caffeine.newBuilder()
073                        .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
074                        .maximumSize(10000)
075                        .build(key -> {
076
077                                String fetchResourceName = key.getResourceName();
078                                if (myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU2) {
079                                        if ("CodeSystem".equals(fetchResourceName)) {
080                                                fetchResourceName = "ValueSet";
081                                        }
082                                }
083                                Class<? extends IBaseResource> fetchResourceType = myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceDefinition(fetchResourceName).getImplementingClass();
084                                IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchResource(fetchResourceType, key.getUri());
085
086                                if (fetched == null) {
087                                        return null;
088                                }
089
090
091                                Resource canonical = myModelConverter.toCanonical(fetched);
092
093                                if (canonical instanceof StructureDefinition) {
094                                        StructureDefinition canonicalSd = (StructureDefinition) canonical;
095                                        if (canonicalSd.getSnapshot().isEmpty()) {
096                                                ourLog.info("Generating snapshot for StructureDefinition: {}", canonicalSd.getUrl());
097                                                fetched = myValidationSupportContext.getRootValidationSupport().generateSnapshot(theValidationSupportContext, fetched, "", null, "");
098                                                Validate.isTrue(fetched != null, "StructureDefinition %s has no snapshot, and no snapshot generator is configured", key.getUri());
099                                                canonical = myModelConverter.toCanonical(fetched);
100                                        }
101                                }
102
103                                return canonical;
104                        });
105
106                setValidationMessageLanguage(getLocale());
107        }
108
109        @Override
110        public List<CanonicalResource> allConformanceResources() {
111                throw new UnsupportedOperationException();
112        }
113
114        @Override
115        public String getLinkForUrl(String corePath, String url) {
116                throw new UnsupportedOperationException();
117        }
118
119        @Override
120        public Map<String, byte[]> getBinaries() {
121                return null;
122        }
123
124        @Override
125        public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException {
126                throw new UnsupportedOperationException();
127        }
128
129        @Override
130        public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FHIRException {
131                throw new UnsupportedOperationException();
132        }
133
134        @Override
135        public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FHIRException {
136                throw new UnsupportedOperationException();
137        }
138
139        @Override
140        public boolean hasPackage(String id, String ver) {
141                throw new UnsupportedOperationException();
142        }
143
144        @Override
145        public int getClientRetryCount() {
146                throw new UnsupportedOperationException();
147        }
148
149        @Override
150        public IWorkerContext setClientRetryCount(int value) {
151                throw new UnsupportedOperationException();
152        }
153
154        @Override
155        public TimeTracker clock() {
156                return null;
157        }
158
159        @Override
160        public void generateSnapshot(StructureDefinition input) throws FHIRException {
161                if (input.hasSnapshot()) {
162                        return;
163                }
164
165                org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider profileKnowledgeProvider = new ProfileKnowledgeWorkerR5(ourR5Context);
166                ArrayList<ValidationMessage> messages = new ArrayList<>();
167                org.hl7.fhir.r5.model.StructureDefinition base = fetchResource(StructureDefinition.class, input.getBaseDefinition());
168                if (base == null) {
169                        throw new PreconditionFailedException("Unknown base definition: " + input.getBaseDefinition());
170                }
171                new org.hl7.fhir.r5.conformance.ProfileUtilities(this, messages, profileKnowledgeProvider).generateSnapshot(base, input, "", null, "");
172
173        }
174
175        @Override
176        public void generateSnapshot(StructureDefinition theStructureDefinition, boolean theB) {
177                // nothing yet
178        }
179
180        @Override
181        public org.hl7.fhir.r5.model.Parameters getExpansionParameters() {
182                return myExpansionProfile;
183        }
184
185        @Override
186        public void setExpansionProfile(org.hl7.fhir.r5.model.Parameters expParameters) {
187                myExpansionProfile = expParameters;
188        }
189
190        @Override
191        public List<StructureDefinition> allStructures() {
192
193                List<StructureDefinition> retVal = myAllStructures;
194                if (retVal == null) {
195                        retVal = new ArrayList<>();
196                        for (IBaseResource next : myValidationSupportContext.getRootValidationSupport().fetchAllStructureDefinitions()) {
197                                try {
198                                        Resource converted = myModelConverter.toCanonical(next);
199                                        retVal.add((StructureDefinition) converted);
200                                } catch (FHIRException e) {
201                                        throw new InternalErrorException(e);
202                                }
203                        }
204                        myAllStructures = retVal;
205                }
206
207                return retVal;
208        }
209
210        @Override
211        public List<StructureDefinition> getStructures() {
212                return allStructures();
213        }
214
215        @Override
216        public void cacheResource(Resource res) {
217                throw new UnsupportedOperationException();
218        }
219
220        @Override
221        public void cacheResourceFromPackage(Resource res, PackageVersion packageDetails) throws FHIRException {
222
223        }
224
225        @Override
226        public void cachePackage(PackageVersion packageDetails, List<PackageVersion> dependencies) {
227
228        }
229
230        @Nonnull
231        private ValidationResult convertValidationResult(@Nullable IValidationSupport.CodeValidationResult theResult) {
232                ValidationResult retVal = null;
233                if (theResult != null) {
234                        String code = theResult.getCode();
235                        String display = theResult.getDisplay();
236                        String issueSeverity = theResult.getSeverityCode();
237                        String message = theResult.getMessage();
238                        if (isNotBlank(code)) {
239                                retVal = new ValidationResult(new org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent()
240                                        .setCode(code)
241                                        .setDisplay(display));
242                        } else if (isNotBlank(issueSeverity)) {
243                                retVal = new ValidationResult(ValidationMessage.IssueSeverity.fromCode(issueSeverity), message, ValueSetExpander.TerminologyServiceErrorClass.UNKNOWN);
244                        }
245
246                }
247
248                if (retVal == null) {
249                        retVal = new ValidationResult(ValidationMessage.IssueSeverity.ERROR, "Validation failed");
250                }
251
252                return retVal;
253        }
254
255        @Override
256        public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean Hierarchical) {
257                IBaseResource convertedSource;
258                try {
259                        convertedSource = myModelConverter.fromCanonical(source);
260                } catch (FHIRException e) {
261                        throw new InternalErrorException(e);
262                }
263                IValidationSupport.ValueSetExpansionOutcome expanded = myValidationSupportContext.getRootValidationSupport().expandValueSet(myValidationSupportContext, null, convertedSource);
264
265                org.hl7.fhir.r5.model.ValueSet convertedResult = null;
266                if (expanded.getValueSet() != null) {
267                        try {
268                                convertedResult = (ValueSet) myModelConverter.toCanonical(expanded.getValueSet());
269                        } catch (FHIRException e) {
270                                throw new InternalErrorException(e);
271                        }
272                }
273
274                String error = expanded.getError();
275                ValueSetExpander.TerminologyServiceErrorClass result = null;
276
277                return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result);
278        }
279
280        @Override
281        public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean Hierarchical) {
282                throw new UnsupportedOperationException();
283        }
284
285        @Override
286        public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException {
287                throw new UnsupportedOperationException();
288        }
289
290        @Override
291        public Locale getLocale() {
292                return myValidationSupportContext.getRootValidationSupport().getFhirContext().getLocalizer().getLocale();
293        }
294
295        @Override
296        public void setLocale(Locale locale) {
297                // ignore
298        }
299
300        @Override
301        public org.hl7.fhir.r5.model.CodeSystem fetchCodeSystem(String system) {
302                IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchCodeSystem(system);
303                if (fetched == null) {
304                        return null;
305                }
306                try {
307                        return (org.hl7.fhir.r5.model.CodeSystem) myModelConverter.toCanonical(fetched);
308                } catch (FHIRException e) {
309                        throw new InternalErrorException(e);
310                }
311        }
312
313        @Override
314        public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
315
316                if (isBlank(uri)) {
317                        return null;
318                }
319
320                ResourceKey key = new ResourceKey(class_.getSimpleName(), uri);
321                @SuppressWarnings("unchecked")
322                T retVal = (T) myFetchResourceCache.get(key);
323
324                return retVal;
325        }
326
327        @Override
328        public Resource fetchResourceById(String type, String uri) {
329                throw new UnsupportedOperationException();
330        }
331
332        @Override
333        public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
334                T retVal = fetchResource(class_, uri);
335                if (retVal == null) {
336                        throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri);
337                }
338                return retVal;
339        }
340
341        @Override
342        public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource canonicalForSource) {
343                throw new UnsupportedOperationException();
344        }
345
346        @Override
347        public List<org.hl7.fhir.r5.model.ConceptMap> findMapsForSource(String url) {
348                throw new UnsupportedOperationException();
349        }
350
351        @Override
352        public String getAbbreviation(String name) {
353                throw new UnsupportedOperationException();
354        }
355
356        @Override
357        public IParser getParser(ParserType type) {
358                throw new UnsupportedOperationException();
359        }
360
361        @Override
362        public IParser getParser(String type) {
363                throw new UnsupportedOperationException();
364        }
365
366        @Override
367        public List<String> getResourceNames() {
368                return new ArrayList<>(myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceTypes());
369        }
370
371        @Override
372        public Set<String> getResourceNamesAsSet() {
373                return myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceTypes();
374        }
375
376        @Override
377        public org.hl7.fhir.r5.model.StructureMap getTransform(String url) {
378                throw new UnsupportedOperationException();
379        }
380
381        @Override
382        public String getOverrideVersionNs() {
383                return null;
384        }
385
386        @Override
387        public void setOverrideVersionNs(String value) {
388                throw new UnsupportedOperationException();
389        }
390
391        @Override
392        public StructureDefinition fetchTypeDefinition(String typeName) {
393                return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
394        }
395
396        @Override
397        public StructureDefinition fetchRawProfile(String url) {
398                StructureDefinition retVal = fetchResource(StructureDefinition.class, url);
399
400                if (retVal != null && retVal.getSnapshot().isEmpty()) {
401                        generateSnapshot(retVal);
402                }
403
404                return retVal;
405        }
406
407        @Override
408        public List<String> getTypeNames() {
409                throw new UnsupportedOperationException();
410        }
411
412        @Override
413        public UcumService getUcumService() {
414                throw new UnsupportedOperationException();
415        }
416
417        @Override
418        public void setUcumService(UcumService ucumService) {
419                throw new UnsupportedOperationException();
420        }
421
422        @Override
423        public String getVersion() {
424                return myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion().getFhirVersionString();
425        }
426
427        @Override
428        public boolean hasCache() {
429                throw new UnsupportedOperationException();
430        }
431
432        @Override
433        public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
434                throw new UnsupportedOperationException();
435        }
436
437        @Override
438        public boolean isNoTerminologyServer() {
439                return false;
440        }
441
442        @Override
443        public List<org.hl7.fhir.r5.model.StructureMap> listTransforms() {
444                throw new UnsupportedOperationException();
445        }
446
447        @Override
448        public IParser newJsonParser() {
449                throw new UnsupportedOperationException();
450        }
451
452        @Override
453        public IResourceValidator newValidator() {
454                throw new UnsupportedOperationException();
455        }
456
457        @Override
458        public IParser newXmlParser() {
459                throw new UnsupportedOperationException();
460        }
461
462        @Override
463        public String oid2Uri(String code) {
464                throw new UnsupportedOperationException();
465        }
466
467        @Override
468        public ILoggingService getLogger() {
469                return null;
470        }
471
472        @Override
473        public void setLogger(ILoggingService logger) {
474                throw new UnsupportedOperationException();
475        }
476
477        @Override
478        public boolean supportsSystem(String system) {
479                return myValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(myValidationSupportContext, system);
480        }
481
482        @Override
483        public TranslationServices translator() {
484                throw new UnsupportedOperationException();
485        }
486
487        @Override
488        public ValidationResult validateCode(ValidationOptions theOptions, String system, String code, String display) {
489                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions);
490
491                return doValidation(null, validationOptions, system, code, display);
492        }
493
494        @Override
495        public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String display, org.hl7.fhir.r5.model.ValueSet theValueSet) {
496                IBaseResource convertedVs = null;
497
498                try {
499                        if (theValueSet != null) {
500                                convertedVs = myModelConverter.fromCanonical(theValueSet);
501                        }
502                } catch (FHIRException e) {
503                        throw new InternalErrorException(e);
504                }
505
506                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions);
507
508                return doValidation(convertedVs, validationOptions, theSystem, theCode, display);
509        }
510
511        @Override
512        public ValidationResult validateCode(ValidationOptions theOptions, String code, org.hl7.fhir.r5.model.ValueSet theValueSet) {
513                IBaseResource convertedVs = null;
514                try {
515                        if (theValueSet != null) {
516                                convertedVs = myModelConverter.fromCanonical(theValueSet);
517                        }
518                } catch (FHIRException e) {
519                        throw new InternalErrorException(e);
520                }
521
522                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions).setInferSystem(true);
523
524                return doValidation(convertedVs, validationOptions, null, code, null);
525        }
526
527        @Override
528        public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.Coding theCoding, org.hl7.fhir.r5.model.ValueSet theValueSet) {
529                IBaseResource convertedVs = null;
530
531                try {
532                        if (theValueSet != null) {
533                                convertedVs = myModelConverter.fromCanonical(theValueSet);
534                        }
535                } catch (FHIRException e) {
536                        throw new InternalErrorException(e);
537                }
538
539                ConceptValidationOptions validationOptions = convertConceptValidationOptions(theOptions);
540                String system = theCoding.getSystem();
541                String code = theCoding.getCode();
542                String display = theCoding.getDisplay();
543
544                return doValidation(convertedVs, validationOptions, system, code, display);
545        }
546
547        @Override
548        public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
549                for (CodingValidationRequest next : codes) {
550                        ValidationResult outcome = validateCode(options, next.getCoding(), vs);
551                        next.setResult(outcome);
552                }
553        }
554
555        @Nonnull
556        private ValidationResult doValidation(IBaseResource theValueSet, ConceptValidationOptions theValidationOptions, String theSystem, String theCode, String theDisplay) {
557                IValidationSupport.CodeValidationResult result;
558                if (theValueSet != null) {
559                        result = myValidationSupportContext.getRootValidationSupport().validateCodeInValueSet(myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, theValueSet);
560                } else {
561                        result = myValidationSupportContext.getRootValidationSupport().validateCode(myValidationSupportContext, theValidationOptions, theSystem, theCode, theDisplay, null);
562                }
563                return convertValidationResult(result);
564        }
565
566        @Override
567        public ValidationResult validateCode(ValidationOptions theOptions, org.hl7.fhir.r5.model.CodeableConcept code, org.hl7.fhir.r5.model.ValueSet theVs) {
568                for (Coding next : code.getCoding()) {
569                        ValidationResult retVal = validateCode(theOptions, next, theVs);
570                        if (retVal.isOk()) {
571                                return retVal;
572                        }
573                }
574
575                return new ValidationResult(ValidationMessage.IssueSeverity.ERROR, null);
576        }
577
578        public void invalidateCaches() {
579                myFetchResourceCache.invalidateAll();
580        }
581
582        public interface IVersionTypeConverter {
583
584                org.hl7.fhir.r5.model.Resource toCanonical(IBaseResource theNonCanonical);
585
586                IBaseResource fromCanonical(org.hl7.fhir.r5.model.Resource theCanonical);
587
588        }
589
590        private static class ResourceKey {
591                private final int myHashCode;
592                private final String myResourceName;
593                private final String myUri;
594
595                private ResourceKey(String theResourceName, String theUri) {
596                        myResourceName = theResourceName;
597                        myUri = theUri;
598                        myHashCode = new HashCodeBuilder(17, 37)
599                                .append(myResourceName)
600                                .append(myUri)
601                                .toHashCode();
602                }
603
604                @Override
605                public boolean equals(Object theO) {
606                        if (this == theO) {
607                                return true;
608                        }
609
610                        if (theO == null || getClass() != theO.getClass()) {
611                                return false;
612                        }
613
614                        ResourceKey that = (ResourceKey) theO;
615
616                        return new EqualsBuilder()
617                                .append(myResourceName, that.myResourceName)
618                                .append(myUri, that.myUri)
619                                .isEquals();
620                }
621
622                public String getResourceName() {
623                        return myResourceName;
624                }
625
626                public String getUri() {
627                        return myUri;
628                }
629
630                @Override
631                public int hashCode() {
632                        return myHashCode;
633                }
634        }
635
636        private static class VersionTypeConverterR5 implements IVersionTypeConverter {
637                @Override
638                public Resource toCanonical(IBaseResource theNonCanonical) {
639                        return (Resource) theNonCanonical;
640                }
641
642                @Override
643                public IBaseResource fromCanonical(Resource theCanonical) {
644                        return theCanonical;
645                }
646        }
647
648        public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) {
649                ConceptValidationOptions retVal = new ConceptValidationOptions();
650                if (theOptions.isGuessSystem()) {
651                        retVal = retVal.setInferSystem(true);
652                }
653                return retVal;
654        }
655
656}
657
658
659