001package org.hl7.fhir.r4.hapi.ctx;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.context.support.IContextValidationSupport;
005import ca.uhn.fhir.rest.api.Constants;
006import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
007import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
008import ca.uhn.fhir.util.CoverageIgnore;
009import com.github.benmanes.caffeine.cache.Cache;
010import com.github.benmanes.caffeine.cache.Caffeine;
011import org.apache.commons.lang3.Validate;
012import org.apache.commons.lang3.time.DateUtils;
013import org.fhir.ucum.UcumService;
014import org.hl7.fhir.exceptions.FHIRException;
015import org.hl7.fhir.exceptions.TerminologyServiceException;
016import org.hl7.fhir.r4.context.IWorkerContext;
017import org.hl7.fhir.r4.formats.IParser;
018import org.hl7.fhir.r4.formats.ParserType;
019import org.hl7.fhir.r4.model.*;
020import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
021import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
022import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
023import org.hl7.fhir.r4.terminologies.ValueSetExpander;
024import org.hl7.fhir.r4.terminologies.ValueSetExpanderFactory;
025import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple;
026import org.hl7.fhir.r4.utils.IResourceValidator;
027import org.hl7.fhir.utilities.TerminologyServiceOptions;
028import org.hl7.fhir.utilities.TranslationServices;
029import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
030
031import java.util.*;
032import java.util.concurrent.TimeUnit;
033
034import static org.apache.commons.lang3.StringUtils.isNotBlank;
035
036public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory {
037  private final FhirContext myCtx;
038  private final Cache<String, Resource> myFetchedResourceCache;
039  private IValidationSupport myValidationSupport;
040  private Parameters myExpansionProfile;
041  private String myOverrideVersionNs;
042
043  public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) {
044    Validate.notNull(theCtx, "theCtx must not be null");
045    Validate.notNull(theValidationSupport, "theValidationSupport must not be null");
046    myCtx = theCtx;
047    myValidationSupport = theValidationSupport;
048
049    long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND;
050    if (System.getProperties().containsKey(ca.uhn.fhir.rest.api.Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) {
051      timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
052    }
053
054    myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
055  }
056
057  @Override
058  public List<StructureDefinition> allStructures() {
059    return myValidationSupport.fetchAllStructureDefinitions(myCtx);
060  }
061
062  @Override
063  public List<StructureDefinition> getStructures() {
064    return allStructures();
065  }
066
067  @Override
068  public CodeSystem fetchCodeSystem(String theSystem) {
069    if (myValidationSupport == null) {
070      return null;
071    } else {
072      return myValidationSupport.fetchCodeSystem(myCtx, theSystem);
073    }
074  }
075
076  @Override
077  public List<ConceptMap> findMapsForSource(String theUrl) {
078    throw new UnsupportedOperationException();
079  }
080
081  @Override
082  public String getAbbreviation(String theName) {
083    throw new UnsupportedOperationException();
084  }
085
086  @Override
087  public ValueSetExpander getExpander() {
088    ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this);
089    retVal.setMaxExpansionSize(Integer.MAX_VALUE);
090    return retVal;
091  }
092
093  @Override
094  public org.hl7.fhir.r4.utils.INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) {
095    throw new UnsupportedOperationException();
096  }
097
098  @Override
099  public IParser getParser(ParserType theType) {
100    throw new UnsupportedOperationException();
101  }
102
103  @Override
104  public IParser getParser(String theType) {
105    throw new UnsupportedOperationException();
106  }
107
108  @Override
109  public List<String> getResourceNames() {
110    List<String> result = new ArrayList<>();
111    for (ResourceType next : ResourceType.values()) {
112      result.add(next.name());
113    }
114    Collections.sort(result);
115    return result;
116  }
117
118  @Override
119  public IParser newJsonParser() {
120    throw new UnsupportedOperationException();
121  }
122
123  @Override
124  public IResourceValidator newValidator() {
125    throw new UnsupportedOperationException();
126  }
127
128  @Override
129  public IParser newXmlParser() {
130    throw new UnsupportedOperationException();
131  }
132
133  @Override
134  public String oid2Uri(String theCode) {
135    throw new UnsupportedOperationException();
136  }
137
138  @Override
139  public boolean supportsSystem(String theSystem) {
140    if (myValidationSupport == null) {
141      return false;
142    } else {
143      return myValidationSupport.isCodeSystemSupported(myCtx, theSystem);
144    }
145  }
146
147  @Override
148  public Set<String> typeTails() {
149    return new HashSet<>(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code",
150      "Markdown", "Base64Binary", "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period", "Ratio", "HumanName", "Address", "ContactPoint",
151      "Timing", "Reference", "Annotation", "Signature", "Meta"));
152  }
153
154  @Override
155  public ValidationResult validateCode(TerminologyServiceOptions theOptions, CodeableConcept theCode, ValueSet theVs) {
156    for (Coding next : theCode.getCoding()) {
157      ValidationResult retVal = validateCode(theOptions, next, theVs);
158      if (retVal.isOk()) {
159        return retVal;
160      }
161    }
162
163    return new ValidationResult(IssueSeverity.ERROR, null);
164  }
165
166  @Override
167  public ValidationResult validateCode(TerminologyServiceOptions theOptions, Coding theCode, ValueSet theVs) {
168    String system = theCode.getSystem();
169    String code = theCode.getCode();
170    String display = theCode.getDisplay();
171    return validateCode(theOptions, system, code, display, theVs);
172  }
173
174  @Override
175  public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) {
176    IContextValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, (String)null);
177    if (result == null) {
178      return null;
179    }
180    return new ValidationResult((IssueSeverity)result.getSeverity(), result.getMessage(), (ConceptDefinitionComponent)result.asConceptDefinition());
181  }
182
183  @Override
184  public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) {
185    throw new UnsupportedOperationException();
186  }
187
188
189  @Override
190  public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay, ValueSet theVs) {
191
192    /*
193     * The following valueset is a special case, since the BCP codesystem is very difficult to expand
194     */
195    if ("http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) {
196      ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
197      definition.setCode(theSystem);
198      definition.setDisplay(theCode);
199      return new ValidationResult(definition);
200    }
201
202    /*
203     * The following valueset is a special case, since the mime types codesystem is very difficult to expand
204     */
205    if ("http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
206      ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
207      definition.setCode(theSystem);
208      definition.setDisplay(theCode);
209      return new ValidationResult(definition);
210    }
211
212    IValidationSupport.CodeValidationResult outcome;
213    if (isNotBlank(theVs.getUrl())) {
214      outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl());
215    } else {
216      outcome = myValidationSupport.validateCodeInValueSet(myCtx, theSystem, theCode, theDisplay, theVs);
217    }
218
219    if (outcome != null && outcome.isOk()) {
220      ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
221      definition.setCode(theCode);
222      definition.setDisplay(outcome.getDisplay());
223      return new ValidationResult(definition);
224    }
225
226    return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + Constants.codeSystemWithDefaultDescription(theSystem) + "]");
227  }
228
229  @Override
230  public ValidationResult validateCode(TerminologyServiceOptions theOptions, String code, ValueSet vs) {
231    return validateCode(theOptions, Constants.CODESYSTEM_VALIDATE_NOT_NEEDED, code, null, vs);
232  }
233
234  @Override
235  @CoverageIgnore
236  public List<MetadataResource> allConformanceResources() {
237    throw new UnsupportedOperationException();
238  }
239
240  @Override
241  public void generateSnapshot(StructureDefinition p) throws FHIRException {
242    throw new UnsupportedOperationException();
243  }
244
245  @Override
246  public Parameters getExpansionParameters() {
247    return myExpansionProfile;
248  }
249
250  @Override
251  public void setExpansionProfile(Parameters theExpParameters) {
252    myExpansionProfile = theExpParameters;
253  }
254
255  @Override
256  @CoverageIgnore
257  public boolean hasCache() {
258    throw new UnsupportedOperationException();
259  }
260
261  @Override
262  public ValueSetExpansionOutcome expand(ValueSet theSource, Parameters theProfile) {
263    ValueSetExpansionOutcome vso;
264    try {
265      vso = getExpander().expand(theSource, theProfile);
266    } catch (InvalidRequestException e) {
267      throw e;
268    } catch (Exception e) {
269      throw new InternalErrorException(e);
270    }
271    if (vso.getError() != null) {
272      throw new InvalidRequestException(vso.getError());
273    } else {
274      return vso;
275    }
276  }
277
278  @Override
279  public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) {
280    throw new UnsupportedOperationException();
281  }
282
283  @Override
284  public ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHeiarchical) throws TerminologyServiceException {
285    return myValidationSupport.expandValueSet(myCtx, theInc);
286  }
287
288  @Override
289  public ILoggingService getLogger() {
290    throw new UnsupportedOperationException();
291  }
292
293  @Override
294  public void setLogger(ILoggingService theLogger) {
295    throw new UnsupportedOperationException();
296  }
297
298  @Override
299  public String getVersion() {
300    return myCtx.getVersion().getVersion().getFhirVersionString();
301  }
302
303  @Override
304  public UcumService getUcumService() {
305    throw new UnsupportedOperationException();
306  }
307
308  @Override
309  public void setUcumService(UcumService ucumService) {
310    throw new UnsupportedOperationException();
311  }
312
313  @Override
314  public boolean isNoTerminologyServer() {
315    return false;
316  }
317
318  @Override
319  public TranslationServices translator() {
320    throw new UnsupportedOperationException();
321  }
322
323  @Override
324  public List<StructureMap> listTransforms() {
325    throw new UnsupportedOperationException();
326  }
327
328  @Override
329  public StructureMap getTransform(String url) {
330    throw new UnsupportedOperationException();
331  }
332
333  @Override
334  public String getOverrideVersionNs() {
335    return myOverrideVersionNs;
336  }
337
338  @Override
339  public void setOverrideVersionNs(String value) {
340    myOverrideVersionNs = value;
341  }
342
343  @Override
344  public StructureDefinition fetchTypeDefinition(String typeName) {
345    return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
346  }
347
348  @Override
349  public String getLinkForUrl(String corePath, String url) {
350    throw new UnsupportedOperationException();
351  }
352
353  @Override
354  public List<String> getTypeNames() {
355    throw new UnsupportedOperationException();
356  }
357
358  @Override
359  public <T extends org.hl7.fhir.r4.model.Resource> T fetchResource(Class<T> theClass, String theUri) {
360    if (myValidationSupport == null) {
361      return null;
362    } else {
363      @SuppressWarnings("unchecked")
364      T retVal = (T) myFetchedResourceCache.get(theUri, t -> {
365        return myValidationSupport.fetchResource(myCtx, theClass, theUri);
366      });
367      return retVal;
368    }
369  }
370
371  @Override
372  public <T extends org.hl7.fhir.r4.model.Resource> T fetchResourceWithException(Class<T> theClass, String theUri) throws FHIRException {
373    T retVal = fetchResource(theClass, theUri);
374    if (retVal == null) {
375      throw new FHIRException("Could not find resource: " + theUri);
376    }
377    return retVal;
378  }
379
380  @Override
381  public org.hl7.fhir.r4.model.Resource fetchResourceById(String theType, String theUri) {
382    throw new UnsupportedOperationException();
383  }
384
385  @Override
386  public <T extends org.hl7.fhir.r4.model.Resource> boolean hasResource(Class<T> theClass_, String theUri) {
387    throw new UnsupportedOperationException();
388  }
389
390  @Override
391  public void cacheResource(org.hl7.fhir.r4.model.Resource theRes) throws FHIRException {
392    throw new UnsupportedOperationException();
393  }
394
395  @Override
396  public Set<String> getResourceNamesAsSet() {
397    return myCtx.getResourceNames();
398  }
399
400  @Override
401  public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHeiarchical) throws FHIRException {
402    throw new UnsupportedOperationException();
403  }
404
405}