001package org.hl7.fhir.r5.hapi.ctx;
002
003import ca.uhn.fhir.i18n.Msg;
004import ca.uhn.fhir.context.FhirContext;
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.util.CoverageIgnore;
010import com.github.benmanes.caffeine.cache.Cache;
011import com.github.benmanes.caffeine.cache.Caffeine;
012import org.apache.commons.lang3.Validate;
013import org.apache.commons.lang3.time.DateUtils;
014import org.fhir.ucum.UcumService;
015import org.hl7.fhir.exceptions.FHIRException;
016import org.hl7.fhir.exceptions.TerminologyServiceException;
017import org.hl7.fhir.r5.context.IWorkerContext;
018import org.hl7.fhir.r5.formats.IParser;
019import org.hl7.fhir.r5.formats.ParserType;
020import org.hl7.fhir.r5.model.CanonicalResource;
021import org.hl7.fhir.r5.model.CodeSystem;
022import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
023import org.hl7.fhir.r5.model.CodeableConcept;
024import org.hl7.fhir.r5.model.Coding;
025import org.hl7.fhir.r5.model.ConceptMap;
026import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
027import org.hl7.fhir.r5.model.Parameters;
028import org.hl7.fhir.r5.model.Resource;
029import org.hl7.fhir.r5.model.ResourceType;
030import org.hl7.fhir.r5.model.StructureDefinition;
031import org.hl7.fhir.r5.model.StructureMap;
032import org.hl7.fhir.r5.model.ValueSet;
033import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
034import org.hl7.fhir.r5.terminologies.ValueSetExpander;
035import org.hl7.fhir.r5.utils.validation.IResourceValidator;
036import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
037import org.hl7.fhir.utilities.TimeTracker;
038import org.hl7.fhir.utilities.TranslationServices;
039import org.hl7.fhir.utilities.i18n.I18nBase;
040import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
041import org.hl7.fhir.utilities.npm.NpmPackage;
042import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
043import org.hl7.fhir.utilities.validation.ValidationOptions;
044
045import java.util.ArrayList;
046import java.util.Collections;
047import java.util.List;
048import java.util.Locale;
049import java.util.Map;
050import java.util.Set;
051import java.util.concurrent.TimeUnit;
052
053import static org.apache.commons.lang3.StringUtils.isNotBlank;
054
055public final class HapiWorkerContext extends I18nBase implements IWorkerContext {
056        private final FhirContext myCtx;
057        private final Cache<String, Resource> myFetchedResourceCache;
058        private final IValidationSupport myValidationSupport;
059        private Parameters myExpansionProfile;
060        private String myOverrideVersionNs;
061
062        public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) {
063                Validate.notNull(theCtx, "theCtx must not be null");
064                Validate.notNull(theValidationSupport, "theValidationSupport must not be null");
065                myCtx = theCtx;
066                myValidationSupport = theValidationSupport;
067
068                long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND;
069                if (System.getProperties().containsKey(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                myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
074
075                // Set a default locale
076                setValidationMessageLanguage(getLocale());
077        }
078
079        @Override
080        public List<StructureDefinition> allStructures() {
081                return myValidationSupport.fetchAllStructureDefinitions();
082        }
083
084        @Override
085        public List<StructureDefinition> getStructures() {
086                return allStructures();
087        }
088
089        @Override
090        public CodeSystem fetchCodeSystem(String theSystem) {
091                if (myValidationSupport == null) {
092                        return null;
093                } else {
094                        return (CodeSystem) myValidationSupport.fetchCodeSystem(theSystem);
095                }
096        }
097
098        @Override
099        public CodeSystem fetchCodeSystem(String theSystem, String version) {
100                if (myValidationSupport == null) {
101                        return null;
102                } else {
103                        return (CodeSystem) myValidationSupport.fetchCodeSystem(theSystem);
104                }
105        }
106
107        @Override
108        public List<ConceptMap> findMapsForSource(String theUrl) {
109                throw new UnsupportedOperationException(Msg.code(201));
110        }
111
112        @Override
113        public String getAbbreviation(String theName) {
114                throw new UnsupportedOperationException(Msg.code(202));
115        }
116
117        @Override
118        public IParser getParser(ParserType theType) {
119                throw new UnsupportedOperationException(Msg.code(203));
120        }
121
122        @Override
123        public IParser getParser(String theType) {
124                throw new UnsupportedOperationException(Msg.code(204));
125        }
126
127        @Override
128        public List<String> getResourceNames() {
129                List<String> result = new ArrayList<>();
130                for (ResourceType next : ResourceType.values()) {
131                        result.add(next.name());
132                }
133                Collections.sort(result);
134                return result;
135        }
136
137        @Override
138        public IParser newJsonParser() {
139                throw new UnsupportedOperationException(Msg.code(205));
140        }
141
142        @Override
143        public IResourceValidator newValidator() {
144                throw new UnsupportedOperationException(Msg.code(206));
145        }
146
147        @Override
148        public IParser newXmlParser() {
149                throw new UnsupportedOperationException(Msg.code(207));
150        }
151
152        @Override
153        public String oid2Uri(String theCode) {
154                throw new UnsupportedOperationException(Msg.code(208));
155        }
156
157        @Override
158        public boolean supportsSystem(String theSystem) {
159                if (myValidationSupport == null) {
160                        return false;
161                } else {
162                        return myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), theSystem);
163                }
164        }
165
166
167        @Override
168        public ValidationResult validateCode(ValidationOptions theOptions, CodeableConcept theCode, ValueSet theVs) {
169                for (Coding next : theCode.getCoding()) {
170                        ValidationResult retVal = validateCode(theOptions, next, theVs);
171                        if (retVal.isOk()) {
172                                return retVal;
173                        }
174                }
175
176                return new ValidationResult(IssueSeverity.ERROR, null);
177        }
178
179        @Override
180        public ValidationResult validateCode(ValidationOptions theOptions, Coding theCode, ValueSet theVs) {
181                String system = theCode.getSystem();
182                String code = theCode.getCode();
183                String display = theCode.getDisplay();
184                return validateCode(theOptions, system, null, code, display, theVs);
185        }
186
187        @Override
188        public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) {
189                return validateCode(options, code, vs);
190        }
191
192        @Override
193        public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
194                throw new UnsupportedOperationException(Msg.code(209));
195        }
196
197        @Override
198        public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet theValueSet, boolean cacheOk, boolean heiarchical, boolean incompleteOk) {
199                return null;
200        }
201
202        @Override
203        public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theVersion,
204                                                                                                         String theCode, String theDisplay) {
205                IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(new ValidationSupportContext(myValidationSupport),
206                        convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, null);
207                if (result == null) {
208                        return null;
209                }
210                IssueSeverity severity = null;
211                if (result.getSeverity() != null) {
212                        severity = IssueSeverity.fromCode(result.getSeverityCode());
213                }
214                ConceptDefinitionComponent definition = new ConceptDefinitionComponent().setCode(result.getCode());
215                return new ValidationResult(severity, result.getMessage(), theSystem, definition);
216        }
217
218        @Override
219        public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theVersion,
220                                                                                                         String theCode, String theDisplay, ValueSet theVs) {
221                IValidationSupport.CodeValidationResult outcome;
222                if (isNotBlank(theVs.getUrl())) {
223                        outcome = myValidationSupport.validateCode(new ValidationSupportContext(myValidationSupport),
224                                convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, theVs.getUrl());
225                } else {
226                        outcome = myValidationSupport.validateCodeInValueSet(new ValidationSupportContext(myValidationSupport),
227                                convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, theVs);
228                }
229
230                if (outcome != null && outcome.isOk()) {
231                        ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
232                        definition.setCode(theCode);
233                        definition.setDisplay(outcome.getDisplay());
234                        return new ValidationResult(theSystem, definition);
235                }
236
237                return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" +
238                        Constants.codeSystemWithDefaultDescription(theSystem) + "]");
239        }
240
241        @Override
242        public ValidationResult validateCode(ValidationOptions theOptions, String code, ValueSet vs) {
243                return validateCode(theOptions, null, null, code, null, vs);
244        }
245
246        @Override
247        @CoverageIgnore
248        public List<CanonicalResource> allConformanceResources() {
249                throw new UnsupportedOperationException(Msg.code(210));
250        }
251
252        @Override
253        public void generateSnapshot(StructureDefinition p) throws FHIRException {
254                myValidationSupport.generateSnapshot(new ValidationSupportContext(myValidationSupport), p, "", "", "");
255        }
256
257        @Override
258        public void generateSnapshot(StructureDefinition mr, boolean ifLogical) {
259
260        }
261
262        @Override
263        public Parameters getExpansionParameters() {
264                return myExpansionProfile;
265        }
266
267        @Override
268        public void setExpansionProfile(Parameters theExpParameters) {
269                myExpansionProfile = theExpParameters;
270        }
271
272        @Override
273        @CoverageIgnore
274        public boolean hasCache() {
275                throw new UnsupportedOperationException(Msg.code(211));
276        }
277
278        @Override
279        public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHierarchical) {
280                throw new UnsupportedOperationException(Msg.code(212));
281        }
282
283        @Override
284        public ValueSetExpander.ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHierarchical, boolean theNoInactive) throws TerminologyServiceException {
285                ValueSet input = new ValueSet();
286                input.getCompose().setInactive(!theNoInactive); //TODO GGG/DO is this valid?
287                input.getCompose().addInclude(theInc);
288                IValidationSupport.ValueSetExpansionOutcome output = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), null, input);
289                return new ValueSetExpander.ValueSetExpansionOutcome((ValueSet) output.getValueSet(), output.getError(), null);
290        }
291
292        @Override
293        public Locale getLocale() {
294                return Locale.getDefault();
295        }
296
297        @Override
298        public void setLocale(Locale locale) {
299                // ignore
300        }
301
302        @Override
303        public ILoggingService getLogger() {
304                throw new UnsupportedOperationException(Msg.code(213));
305        }
306
307        @Override
308        public void setLogger(ILoggingService theLogger) {
309                throw new UnsupportedOperationException(Msg.code(214));
310        }
311
312        @Override
313        public String getVersion() {
314                return myCtx.getVersion().getVersion().getFhirVersionString();
315        }
316
317        @Override
318        public String getSpecUrl() {
319                throw new UnsupportedOperationException(Msg.code(215));
320        }
321
322        @Override
323        public UcumService getUcumService() {
324                throw new UnsupportedOperationException(Msg.code(216));
325        }
326
327        @Override
328        public void setUcumService(UcumService ucumService) {
329                throw new UnsupportedOperationException(Msg.code(217));
330        }
331
332        @Override
333        public boolean isNoTerminologyServer() {
334                return false;
335        }
336
337        @Override
338        public Set<String> getCodeSystemsUsed() {
339                throw new UnsupportedOperationException(Msg.code(218));
340        }
341
342        @Override
343        public TranslationServices translator() {
344                throw new UnsupportedOperationException(Msg.code(219));
345        }
346
347        @Override
348        public List<StructureMap> listTransforms() {
349                throw new UnsupportedOperationException(Msg.code(220));
350        }
351
352        @Override
353        public StructureMap getTransform(String url) {
354                throw new UnsupportedOperationException(Msg.code(221));
355        }
356
357        @Override
358        public String getOverrideVersionNs() {
359                return myOverrideVersionNs;
360        }
361
362        @Override
363        public void setOverrideVersionNs(String value) {
364                myOverrideVersionNs = value;
365        }
366
367        @Override
368        public StructureDefinition fetchTypeDefinition(String typeName) {
369                return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
370        }
371
372        @Override
373        public StructureDefinition fetchRawProfile(String url) {
374                throw new UnsupportedOperationException(Msg.code(222));
375        }
376
377        @Override
378        public List<String> getTypeNames() {
379                throw new UnsupportedOperationException(Msg.code(223));
380        }
381
382        @Override
383        public <T extends org.hl7.fhir.r5.model.Resource> T fetchResource(Class<T> theClass, String theUri) {
384                if (myValidationSupport == null || theUri == null) {
385                        return null;
386                } else {
387                        @SuppressWarnings("unchecked")
388                        T retVal = (T) myFetchedResourceCache.get(theUri, t -> myValidationSupport.fetchResource(theClass, theUri));
389                        return retVal;
390                }
391        }
392
393        @Override
394        public <T extends org.hl7.fhir.r5.model.Resource> T fetchResourceWithException(Class<T> theClass, String theUri) throws FHIRException {
395                T retVal = fetchResource(theClass, theUri);
396                if (retVal == null) {
397                        throw new FHIRException(Msg.code(224) + "Could not find resource: " + theUri);
398                }
399                return retVal;
400        }
401
402        @Override
403        public <T extends Resource> T fetchResource(Class<T> theClass, String theUri, String theVersion) {
404                return fetchResource(theClass, theUri + "|" + theVersion);
405        }
406
407        @Override
408        public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource canonicalForSource) {
409                throw new UnsupportedOperationException(Msg.code(225));
410        }
411
412        @Override
413        public org.hl7.fhir.r5.model.Resource fetchResourceById(String theType, String theUri) {
414                throw new UnsupportedOperationException(Msg.code(226));
415        }
416
417        @Override
418        public <T extends org.hl7.fhir.r5.model.Resource> boolean hasResource(Class<T> theClass_, String theUri) {
419                throw new UnsupportedOperationException(Msg.code(227));
420        }
421
422        @Override
423        public void cacheResource(org.hl7.fhir.r5.model.Resource theRes) throws FHIRException {
424                throw new UnsupportedOperationException(Msg.code(228));
425        }
426
427        @Override
428        public void cacheResourceFromPackage(Resource res, PackageVersion packageDetails) throws FHIRException {
429                throw new UnsupportedOperationException(Msg.code(229));
430        }
431
432        @Override
433        public void cachePackage(PackageDetails packageDetails, List<PackageVersion> list) {
434
435        }
436
437        @Override
438        public Set<String> getResourceNamesAsSet() {
439                return myCtx.getResourceTypes();
440        }
441
442        @Override
443        public ValueSetExpander.ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHierarchical) throws FHIRException {
444                throw new UnsupportedOperationException(Msg.code(230));
445        }
446
447
448        @Override
449        public String getLinkForUrl(String corePath, String url) {
450                throw new UnsupportedOperationException(Msg.code(231));
451        }
452
453        @Override
454        public Map<String, byte[]> getBinaries() {
455                throw new UnsupportedOperationException(Msg.code(232));
456        }
457
458        @Override
459        public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException {
460                throw new UnsupportedOperationException(Msg.code(233));
461        }
462
463        @Override
464        public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FHIRException {
465                throw new UnsupportedOperationException(Msg.code(234));
466        }
467
468        @Override
469        public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FHIRException {
470                throw new UnsupportedOperationException(Msg.code(235));
471        }
472
473        @Override
474        public boolean hasPackage(String id, String ver) {
475                throw new UnsupportedOperationException(Msg.code(236));
476        }
477
478        @Override
479        public boolean hasPackage(PackageVersion packageVersion) {
480                return false;
481        }
482
483        @Override
484        public PackageDetails getPackage(PackageVersion packageVersion) {
485                return null;
486        }
487
488        @Override
489        public int getClientRetryCount() {
490                throw new UnsupportedOperationException(Msg.code(237));
491        }
492
493        @Override
494        public IWorkerContext setClientRetryCount(int value) {
495                throw new UnsupportedOperationException(Msg.code(238));
496        }
497
498        @Override
499        public TimeTracker clock() {
500                return null;
501        }
502
503        @Override
504        public PackageVersion getPackageForUrl(String s) {
505                return null;
506        }
507
508        public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) {
509                ConceptValidationOptions retVal = new ConceptValidationOptions();
510                if (theOptions.isGuessSystem()) {
511                        retVal = retVal.setInferSystem(true);
512                }
513                return retVal;
514        }
515
516}