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