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