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