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