001package org.hl7.fhir.dstu3.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.hl7.fhir.dstu3.context.IWorkerContext;
013import org.hl7.fhir.dstu3.formats.IParser;
014import org.hl7.fhir.dstu3.formats.ParserType;
015import org.hl7.fhir.dstu3.model.*;
016import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
017import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
018import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
019import org.hl7.fhir.dstu3.terminologies.ValueSetExpander;
020import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderFactory;
021import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderSimple;
022import org.hl7.fhir.dstu3.utils.INarrativeGenerator;
023import org.hl7.fhir.dstu3.utils.IResourceValidator;
024import org.hl7.fhir.exceptions.FHIRException;
025import org.hl7.fhir.exceptions.TerminologyServiceException;
026import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
027
028import java.util.*;
029import java.util.concurrent.TimeUnit;
030
031import static org.apache.commons.lang3.StringUtils.isNotBlank;
032
033public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory {
034  private final FhirContext myCtx;
035  private final Cache<String, Resource> myFetchedResourceCache;
036
037  private IValidationSupport myValidationSupport;
038  private ExpansionProfile myExpansionProfile;
039
040  public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) {
041    Validate.notNull(theCtx, "theCtx must not be null");
042    Validate.notNull(theValidationSupport, "theValidationSupport must not be null");
043    myCtx = theCtx;
044    myValidationSupport = theValidationSupport;
045
046    long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND;
047    if (System.getProperties().containsKey(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) {
048      timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS));
049    }
050    myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build();
051  }
052
053  @Override
054  @CoverageIgnore
055  public List<MetadataResource> allConformanceResources() {
056    throw new UnsupportedOperationException();
057  }
058
059  @Override
060  public List<StructureDefinition> allStructures() {
061    return myValidationSupport.fetchAllStructureDefinitions(myCtx);
062  }
063
064  @Override
065  public ValueSetExpansionOutcome expand(ValueSet theSource, ExpansionProfile theProfile) {
066    ValueSetExpansionOutcome vso;
067    try {
068      vso = getExpander().expand(theSource, theProfile);
069    } catch (InvalidRequestException e) {
070      throw e;
071    } catch (TerminologyServiceException e) {
072      throw new InvalidRequestException(e.getMessage(), e);
073    } catch (Exception e) {
074      throw new InternalErrorException(e);
075    }
076    if (vso.getError() != null) {
077      throw new InvalidRequestException(vso.getError());
078    } else {
079      return vso;
080    }
081  }
082
083  @Override
084  public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) {
085    throw new UnsupportedOperationException();
086  }
087
088  @Override
089  public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc, boolean theHeiarchical) {
090    return myValidationSupport.expandValueSet(myCtx, theInc);
091  }
092
093  @Override
094  public StructureDefinition fetchTypeDefinition(String theCode) {
095    return fetchResource(org.hl7.fhir.dstu3.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + theCode);
096  }
097
098  @Override
099  public CodeSystem fetchCodeSystem(String theSystem) {
100    if (myValidationSupport == null) {
101      return null;
102    } else {
103      return myValidationSupport.fetchCodeSystem(myCtx, theSystem);
104    }
105  }
106
107  @Override
108  public <T extends Resource> T fetchResource(Class<T> theClass, String theUri) {
109    if (myValidationSupport == null) {
110      return null;
111    } else {
112      @SuppressWarnings("unchecked")
113      T retVal = (T) myFetchedResourceCache.get(theUri, t->{
114        return myValidationSupport.fetchResource(myCtx, theClass, theUri);
115      });
116      return retVal;
117    }
118  }
119
120  @Override
121  public <T extends Resource> T fetchResourceWithException(Class<T> theClass_, String theUri) throws FHIRException {
122    T retVal = fetchResource(theClass_, theUri);
123    if (retVal == null) {
124      throw new FHIRException("Unable to fetch " + theUri);
125    }
126    return retVal;
127  }
128
129  @Override
130  public List<ConceptMap> findMapsForSource(String theUrl) {
131    throw new UnsupportedOperationException();
132  }
133
134  @Override
135  public String getAbbreviation(String theName) {
136    throw new UnsupportedOperationException();
137  }
138
139  @Override
140  public ValueSetExpander getExpander() {
141    ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this, this);
142    retVal.setMaxExpansionSize(Integer.MAX_VALUE);
143    return retVal;
144  }
145
146  @Override
147  public ExpansionProfile getExpansionProfile() {
148    return myExpansionProfile;
149  }
150
151  @Override
152  public void setExpansionProfile(ExpansionProfile theExpProfile) {
153    myExpansionProfile = theExpProfile;
154  }
155
156  @Override
157  public INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) {
158    throw new UnsupportedOperationException();
159  }
160
161  @Override
162  public IResourceValidator newValidator() throws FHIRException {
163    throw new UnsupportedOperationException();
164  }
165
166  @Override
167  public IParser getParser(ParserType theType) {
168    throw new UnsupportedOperationException();
169  }
170
171  @Override
172  public IParser getParser(String theType) {
173    throw new UnsupportedOperationException();
174  }
175
176  @Override
177  public List<String> getResourceNames() {
178    List<String> result = new ArrayList<String>();
179    for (ResourceType next : ResourceType.values()) {
180      result.add(next.name());
181    }
182    Collections.sort(result);
183    return result;
184  }
185
186  @Override
187  public Set<String> getResourceNamesAsSet() {
188    return new HashSet<>(getResourceNames());
189  }
190
191  @Override
192  public List<String> getTypeNames() {
193    throw new UnsupportedOperationException();
194  }
195
196  @Override
197  public String getVersion() {
198    return myCtx.getVersion().getVersion().getFhirVersionString();
199  }
200
201  @Override
202  @CoverageIgnore
203  public boolean hasCache() {
204    throw new UnsupportedOperationException();
205  }
206
207  @Override
208  public <T extends Resource> boolean hasResource(Class<T> theClass_, String theUri) {
209    throw new UnsupportedOperationException();
210  }
211
212  @Override
213  public boolean isNoTerminologyServer() {
214    return false;
215  }
216
217  @Override
218  public IParser newJsonParser() {
219    throw new UnsupportedOperationException();
220  }
221
222  @Override
223  public IParser newXmlParser() {
224    throw new UnsupportedOperationException();
225  }
226
227  @Override
228  public String oid2Uri(String theCode) {
229    throw new UnsupportedOperationException();
230  }
231
232  @Override
233  public void setLogger(ILoggingService theLogger) {
234    throw new UnsupportedOperationException();
235  }
236
237  @Override
238  public boolean supportsSystem(String theSystem) {
239    if (myValidationSupport == null) {
240      return false;
241    } else {
242      return myValidationSupport.isCodeSystemSupported(myCtx, theSystem);
243    }
244  }
245
246  @Override
247  public Set<String> typeTails() {
248    return new HashSet<String>(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code",
249      "Markdown", "Base64Binary", "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period", "Ratio", "HumanName", "Address", "ContactPoint",
250      "Timing", "Reference", "Annotation", "Signature", "Meta"));
251  }
252
253  @Override
254  public ValidationResult validateCode(CodeableConcept theCode, ValueSet theVs) {
255    for (Coding next : theCode.getCoding()) {
256      ValidationResult retVal = validateCode(next, theVs);
257      if (retVal.isOk()) {
258        return retVal;
259      }
260    }
261
262    return new ValidationResult(IssueSeverity.ERROR, null);
263  }
264
265  @Override
266  public ValidationResult validateCode(Coding theCode, ValueSet theVs) {
267    String system = theCode.getSystem();
268    String code = theCode.getCode();
269    String display = theCode.getDisplay();
270    return validateCode(system, code, display, theVs);
271  }
272
273  @Override
274  public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) {
275    IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, (String)null);
276    if (result == null) {
277      return null;
278    }
279    return new ValidationResult((IssueSeverity)result.getSeverity(), result.getMessage(), (ConceptDefinitionComponent)result.asConceptDefinition());
280  }
281
282  @Override
283  public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) {
284    throw new UnsupportedOperationException();
285  }
286
287  @Override
288  public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ValueSet theVs) {
289
290    /*
291     * The following valueset is a special case, since the BCP codesystem is very difficult to expand
292     */
293    if ("http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) {
294      ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
295      definition.setCode(theSystem);
296      definition.setDisplay(theCode);
297      return new ValidationResult(definition);
298    }
299
300    /*
301     * The following valueset is a special case, since the mime types codesystem is very difficult to expand
302     */
303    if ("http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
304      ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
305      definition.setCode(theSystem);
306      definition.setDisplay(theCode);
307      return new ValidationResult(definition);
308    }
309
310    IValidationSupport.CodeValidationResult outcome;
311    if (isNotBlank(theVs.getUrl())) {
312      outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl());
313    } else {
314      outcome = myValidationSupport.validateCodeInValueSet(myCtx, theSystem, theCode, theDisplay, theVs);
315    }
316
317    if (outcome != null && outcome.isOk()) {
318      ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
319      definition.setCode(theCode);
320      definition.setDisplay(outcome.getDisplay());
321      return new ValidationResult(definition);
322    }
323
324    return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + Constants.codeSystemWithDefaultDescription(theSystem) + "]");
325  }
326
327}