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