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.ValueSetExpansionComponent;
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.TranslationServices;
031import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
032
033import java.util.*;
034import java.util.concurrent.TimeUnit;
035
036import static org.apache.commons.lang3.StringUtils.isBlank;
037import static org.apache.commons.lang3.StringUtils.isNotBlank;
038
039public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory {
040  private final FhirContext myCtx;
041  private final Cache<String, Resource> myFetchedResourceCache;
042  private IValidationSupport myValidationSupport;
043  private ExpansionProfile myExpansionProfile;
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, 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<String>();
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<String>(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(null, 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    if (expandedValueSet == null) {
234      expandedValueSet = expand(theVs, null);
235    }
236
237    for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
238      String nextCode = next.getCode();
239      if (!caseSensitive) {
240        nextCode = nextCode.toUpperCase();
241      }
242
243      if (nextCode.equals(wantCode)) {
244        if (theSystem == null || next.getSystem().equals(theSystem)) {
245          ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
246          definition.setCode(next.getCode());
247          definition.setDisplay(next.getDisplay());
248          ValidationResult retVal = new ValidationResult(definition);
249          return retVal;
250        }
251      }
252    }
253
254    return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]");
255  }
256
257  @Override
258  @CoverageIgnore
259  public List<MetadataResource> allConformanceResources() {
260    throw new UnsupportedOperationException();
261  }
262
263  @Override
264  @CoverageIgnore
265  public boolean hasCache() {
266    throw new UnsupportedOperationException();
267  }
268
269  @Override
270  public ValueSetExpansionOutcome expand(ValueSet theSource, ExpansionProfile theProfile) {
271    ValueSetExpansionOutcome vso;
272    try {
273      vso = getExpander().expand(theSource, theProfile);
274    } catch (InvalidRequestException e) {
275      throw e;
276    } catch (Exception e) {
277      throw new InternalErrorException(e);
278    }
279    if (vso.getError() != null) {
280      throw new InvalidRequestException(vso.getError());
281    } else {
282      return vso;
283    }
284  }
285
286  @Override
287  public ExpansionProfile getExpansionProfile() {
288    return myExpansionProfile;
289  }
290
291  @Override
292  public void setExpansionProfile(ExpansionProfile theExpProfile) {
293    myExpansionProfile = theExpProfile;
294  }
295
296  @Override
297  public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) {
298    throw new UnsupportedOperationException();
299  }
300
301  @Override
302  public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc, boolean theHeiarchical) throws TerminologyServiceException {
303    return myValidationSupport.expandValueSet(myCtx, theInc);
304  }
305
306  @Override
307  public void setLogger(ILoggingService theLogger) {
308    throw new UnsupportedOperationException();
309  }
310
311  @Override
312  public String getVersion() {
313    return myCtx.getVersion().getVersion().getFhirVersionString();
314  }
315
316  @Override
317  public UcumService getUcumService() {
318    throw new UnsupportedOperationException();
319  }
320
321  @Override
322  public boolean isNoTerminologyServer() {
323    return false;
324  }
325
326  @Override
327  public TranslationServices translator() {
328    throw new UnsupportedOperationException();
329  }
330
331  @Override
332  public List<StructureMap> listTransforms() {
333    throw new UnsupportedOperationException();
334  }
335
336  @Override
337  public StructureMap getTransform(String url) {
338    throw new UnsupportedOperationException();
339  }
340
341  @Override
342  public List<String> getTypeNames() {
343    throw new UnsupportedOperationException();
344  }
345
346  @Override
347  public <T extends org.hl7.fhir.r4.model.Resource> T fetchResource(Class<T> theClass, String theUri) {
348    if (myValidationSupport == null) {
349      return null;
350    } else {
351      @SuppressWarnings("unchecked")
352      T retVal = (T) myFetchedResourceCache.get(theUri, t -> {
353        return myValidationSupport.fetchResource(myCtx, theClass, theUri);
354      });
355      return retVal;
356    }
357  }
358
359  @Override
360  public <T extends org.hl7.fhir.r4.model.Resource> T fetchResourceWithException(Class<T> theClass, String theUri) throws FHIRException {
361    T retVal = fetchResource(theClass, theUri);
362    if (retVal == null) {
363      throw new FHIRException("Could not find resource: " + theUri);
364    }
365    return retVal;
366  }
367
368  @Override
369  public org.hl7.fhir.r4.model.Resource fetchResourceById(String theType, String theUri) {
370    throw new UnsupportedOperationException();
371  }
372
373  @Override
374  public <T extends org.hl7.fhir.r4.model.Resource> boolean hasResource(Class<T> theClass_, String theUri) {
375    throw new UnsupportedOperationException();
376  }
377
378  @Override
379  public void cacheResource(org.hl7.fhir.r4.model.Resource theRes) throws FHIRException {
380    throw new UnsupportedOperationException();
381  }
382
383  @Override
384  public Set<String> getResourceNamesAsSet() {
385    return myCtx.getResourceNames();
386  }
387
388  @Override
389  public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHeiarchical) throws FHIRException {
390    throw new UnsupportedOperationException();
391  }
392
393}