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