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