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