001package org.hl7.fhir.dstu2.utils;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032import java.io.FileNotFoundException;
033import java.io.IOException;
034import java.util.ArrayList;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Locale;
039import java.util.Map;
040import java.util.ResourceBundle;
041import java.util.Set;
042
043import org.hl7.fhir.dstu2.model.BooleanType;
044import org.hl7.fhir.dstu2.model.CodeableConcept;
045import org.hl7.fhir.dstu2.model.Coding;
046import org.hl7.fhir.dstu2.model.ConceptMap;
047import org.hl7.fhir.dstu2.model.Conformance;
048import org.hl7.fhir.dstu2.model.Extension;
049import org.hl7.fhir.dstu2.model.IntegerType;
050import org.hl7.fhir.dstu2.model.Parameters;
051import org.hl7.fhir.dstu2.model.Parameters.ParametersParameterComponent;
052import org.hl7.fhir.dstu2.model.Reference;
053import org.hl7.fhir.dstu2.model.StringType;
054import org.hl7.fhir.dstu2.model.StructureDefinition;
055import org.hl7.fhir.dstu2.model.UriType;
056import org.hl7.fhir.dstu2.model.ValueSet;
057import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionComponent;
058import org.hl7.fhir.dstu2.model.ValueSet.ConceptDefinitionDesignationComponent;
059import org.hl7.fhir.dstu2.model.ValueSet.ConceptSetComponent;
060import org.hl7.fhir.dstu2.model.ValueSet.ValueSetComposeComponent;
061import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent;
062import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent;
063import org.hl7.fhir.dstu2.terminologies.ValueSetExpander.ETooCostly;
064import org.hl7.fhir.dstu2.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
065import org.hl7.fhir.dstu2.terminologies.ValueSetExpanderFactory;
066import org.hl7.fhir.dstu2.terminologies.ValueSetExpansionCache;
067import org.hl7.fhir.dstu2.utils.client.FHIRToolingClient;
068import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
069import org.hl7.fhir.utilities.UUIDUtilities;
070import org.hl7.fhir.utilities.i18n.I18nBase;
071import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
072
073@Deprecated
074public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext {
075
076  // all maps are to the full URI
077  protected Map<String, ValueSet> codeSystems = new HashMap<String, ValueSet>();
078  protected Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>();
079  protected Map<String, ConceptMap> maps = new HashMap<String, ConceptMap>();
080
081  protected ValueSetExpanderFactory expansionCache = new ValueSetExpansionCache(this);
082  protected boolean cacheValidation; // if true, do an expansion and cache the expansion
083  private Set<String> failed = new HashSet<String>(); // value sets for which we don't try to do expansion, since the
084                                                      // first attempt to get a comprehensive expansion was not
085                                                      // successful
086  protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String, ValidationResult>>();
087
088  // private ValueSetExpansionCache expansionCache; //
089
090  protected FHIRToolingClient txServer;
091  private Locale locale;
092  private ResourceBundle i18Nmessages;
093
094  @Override
095  public ValueSet fetchCodeSystem(String system) {
096    return codeSystems.get(system);
097  }
098
099  @Override
100  public boolean supportsSystem(String system) {
101    if (codeSystems.containsKey(system))
102      return true;
103    else {
104      Conformance conf = txServer.getConformanceStatement();
105      for (Extension ex : ToolingExtensions.getExtensions(conf,
106          "http://hl7.org/fhir/StructureDefinition/conformance-supported-system")) {
107        if (system.equals(((UriType) ex.getValue()).getValue())) {
108          return true;
109        }
110      }
111    }
112    return false;
113  }
114
115  @Override
116  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk) {
117    try {
118      Parameters params = new Parameters();
119      params.addParameter().setName("_limit").setValue(new IntegerType("10000"));
120      params.addParameter().setName("_incomplete").setValue(new BooleanType("true"));
121      params.addParameter().setName("profile").setValue(new UriType("http://www.healthintersections.com.au/fhir/expansion/no-details"));
122      ValueSet result = txServer.expandValueset(vs, params);
123      return new ValueSetExpansionOutcome(result);
124    } catch (Exception e) {
125      return new ValueSetExpansionOutcome("Error expanding ValueSet \"" + vs.getUrl() + ": " + e.getMessage());
126    }
127  }
128
129  private ValidationResult handleByCache(ValueSet vs, Coding coding, boolean tryCache) {
130    String cacheId = cacheId(coding);
131    Map<String, ValidationResult> cache = validationCache.get(vs.getUrl());
132    if (cache == null) {
133      cache = new HashMap<String, IWorkerContext.ValidationResult>();
134      validationCache.put(vs.getUrl(), cache);
135    }
136    if (cache.containsKey(cacheId))
137      return cache.get(cacheId);
138    if (!tryCache)
139      return null;
140    if (!cacheValidation)
141      return null;
142    if (failed.contains(vs.getUrl()))
143      return null;
144    ValueSetExpansionOutcome vse = expandVS(vs, true);
145    if (vse.getValueset() == null || notcomplete(vse.getValueset())) {
146      failed.add(vs.getUrl());
147      return null;
148    }
149
150    ValidationResult res = validateCode(coding, vse.getValueset());
151    cache.put(cacheId, res);
152    return res;
153  }
154
155  private boolean notcomplete(ValueSet vs) {
156    if (!vs.hasExpansion())
157      return true;
158    if (!vs.getExpansion().getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/valueset-unclosed").isEmpty())
159      return true;
160    if (!vs.getExpansion().getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/valueset-toocostly").isEmpty())
161      return true;
162    return false;
163  }
164
165  private ValidationResult handleByCache(ValueSet vs, CodeableConcept concept, boolean tryCache) {
166    String cacheId = cacheId(concept);
167    Map<String, ValidationResult> cache = validationCache.get(vs.getUrl());
168    if (cache == null) {
169      cache = new HashMap<String, IWorkerContext.ValidationResult>();
170      validationCache.put(vs.getUrl(), cache);
171    }
172    if (cache.containsKey(cacheId))
173      return cache.get(cacheId);
174
175    if (validationCache.containsKey(vs.getUrl()) && validationCache.get(vs.getUrl()).containsKey(cacheId))
176      return validationCache.get(vs.getUrl()).get(cacheId);
177    if (!tryCache)
178      return null;
179    if (!cacheValidation)
180      return null;
181    if (failed.contains(vs.getUrl()))
182      return null;
183    ValueSetExpansionOutcome vse = expandVS(vs, true);
184    if (vse.getValueset() == null || notcomplete(vse.getValueset())) {
185      failed.add(vs.getUrl());
186      return null;
187    }
188    ValidationResult res = validateCode(concept, vse.getValueset());
189    cache.put(cacheId, res);
190    return res;
191  }
192
193  private String cacheId(Coding coding) {
194    return "|" + coding.getSystem() + "|" + coding.getVersion() + "|" + coding.getCode() + "|" + coding.getDisplay();
195  }
196
197  private String cacheId(CodeableConcept cc) {
198    StringBuilder b = new StringBuilder();
199    for (Coding c : cc.getCoding()) {
200      b.append("#");
201      b.append(cacheId(c));
202    }
203    return b.toString();
204  }
205
206  private ValidationResult verifyCodeExternal(ValueSet vs, Coding coding, boolean tryCache) {
207    ValidationResult res = handleByCache(vs, coding, tryCache);
208    if (res != null)
209      return res;
210    Parameters pin = new Parameters();
211    pin.addParameter().setName("coding").setValue(coding);
212    pin.addParameter().setName("valueSet").setResource(vs);
213    res = serverValidateCode(pin);
214    Map<String, ValidationResult> cache = validationCache.get(vs.getUrl());
215    cache.put(cacheId(coding), res);
216    return res;
217  }
218
219  private ValidationResult verifyCodeExternal(ValueSet vs, CodeableConcept cc, boolean tryCache) {
220    ValidationResult res = handleByCache(vs, cc, tryCache);
221    if (res != null)
222      return res;
223    Parameters pin = new Parameters();
224    pin.addParameter().setName("codeableConcept").setValue(cc);
225    pin.addParameter().setName("valueSet").setResource(vs);
226    res = serverValidateCode(pin);
227    Map<String, ValidationResult> cache = validationCache.get(vs.getUrl());
228    cache.put(cacheId(cc), res);
229    return res;
230  }
231
232  private ValidationResult serverValidateCode(Parameters pin) {
233    Parameters pout = txServer.operateType(ValueSet.class, "validate-code", pin);
234    boolean ok = false;
235    String message = "No Message returned";
236    String display = null;
237    for (ParametersParameterComponent p : pout.getParameter()) {
238      if (p.getName().equals("result"))
239        ok = ((BooleanType) p.getValue()).getValue().booleanValue();
240      else if (p.getName().equals("message"))
241        message = ((StringType) p.getValue()).getValue();
242      else if (p.getName().equals("display"))
243        display = ((StringType) p.getValue()).getValue();
244    }
245    if (!ok)
246      return new ValidationResult(IssueSeverity.ERROR, message);
247    else if (display != null)
248      return new ValidationResult(new ConceptDefinitionComponent().setDisplay(display));
249    else
250      return new ValidationResult(null);
251  }
252
253  @Override
254  public ValueSetExpansionComponent expandVS(ConceptSetComponent inc) {
255    ValueSet vs = new ValueSet();
256    vs.setCompose(new ValueSetComposeComponent());
257    vs.getCompose().getInclude().add(inc);
258    ValueSetExpansionOutcome vse = expandVS(vs, true);
259    return vse.getValueset().getExpansion();
260  }
261
262  @Override
263  public ValidationResult validateCode(String system, String code, String display) {
264    try {
265      if (codeSystems.containsKey(system))
266        return verifyCodeInternal(codeSystems.get(system), system, code, display);
267      else
268        return verifyCodeExternal(null, new Coding().setSystem(system).setCode(code).setDisplay(display), true);
269    } catch (Exception e) {
270      return new ValidationResult(IssueSeverity.FATAL,
271          "Error validating code \"" + code + "\" in system \"" + system + "\": " + e.getMessage());
272    }
273  }
274
275  @Override
276  public ValidationResult validateCode(Coding code, ValueSet vs) {
277    try {
278      if (codeSystems.containsKey(code.getSystem()) || vs.hasExpansion())
279        return verifyCodeInternal(codeSystems.get(code.getSystem()), code.getSystem(), code.getCode(),
280            code.getDisplay());
281      else
282        return verifyCodeExternal(vs, code, true);
283    } catch (Exception e) {
284      return new ValidationResult(IssueSeverity.FATAL,
285          "Error validating code \"" + code + "\" in system \"" + code.getSystem() + "\": " + e.getMessage());
286    }
287  }
288
289  @Override
290  public ValidationResult validateCode(CodeableConcept code, ValueSet vs) {
291    try {
292      if (vs.hasCodeSystem() || vs.hasExpansion())
293        return verifyCodeInternal(vs, code);
294      else
295        return verifyCodeExternal(vs, code, true);
296    } catch (Exception e) {
297      return new ValidationResult(IssueSeverity.FATAL,
298          "Error validating code \"" + code.toString() + "\": " + e.getMessage());
299    }
300  }
301
302  @Override
303  public ValidationResult validateCode(String system, String code, String display, ValueSet vs) {
304    try {
305      if (system == null && vs.hasCodeSystem())
306        return verifyCodeInternal(vs, vs.getCodeSystem().getSystem(), code, display);
307      else if (codeSystems.containsKey(system) || vs.hasExpansion())
308        return verifyCodeInternal(vs, system, code, display);
309      else
310        return verifyCodeExternal(vs, new Coding().setSystem(system).setCode(code).setDisplay(display), true);
311    } catch (Exception e) {
312      return new ValidationResult(IssueSeverity.FATAL,
313          "Error validating code \"" + code + "\" in system \"" + system + "\": " + e.getMessage());
314    }
315  }
316
317  @Override
318  public ValidationResult validateCode(String system, String code, String display, ConceptSetComponent vsi) {
319    try {
320      ValueSet vs = new ValueSet().setUrl(UUIDUtilities.makeUuidUrn());
321      vs.getCompose().addInclude(vsi);
322      return verifyCodeExternal(vs, new Coding().setSystem(system).setCode(code).setDisplay(display), true);
323    } catch (Exception e) {
324      return new ValidationResult(IssueSeverity.FATAL,
325          "Error validating code \"" + code + "\" in system \"" + system + "\": " + e.getMessage());
326    }
327  }
328
329  @Override
330  public List<ConceptMap> findMapsForSource(String url) {
331    List<ConceptMap> res = new ArrayList<ConceptMap>();
332    for (ConceptMap map : maps.values())
333      if (((Reference) map.getSource()).getReference().equals(url))
334        res.add(map);
335    return res;
336  }
337
338  private ValidationResult verifyCodeInternal(ValueSet vs, CodeableConcept code)
339      throws FileNotFoundException, ETooCostly, IOException {
340    for (Coding c : code.getCoding()) {
341      ValidationResult res = verifyCodeInternal(vs, c.getSystem(), c.getCode(), c.getDisplay());
342      if (res.isOk())
343        return res;
344    }
345    if (code.getCoding().isEmpty())
346      return new ValidationResult(IssueSeverity.ERROR, "None code provided");
347    else
348      return new ValidationResult(IssueSeverity.ERROR, "None of the codes are in the specified value set");
349  }
350
351  private ValidationResult verifyCodeInternal(ValueSet vs, String system, String code, String display)
352      throws FileNotFoundException, ETooCostly, IOException {
353    if (vs.hasExpansion())
354      return verifyCodeInExpansion(vs, system, code, display);
355    else if (vs.hasCodeSystem() && !vs.hasCompose())
356      return verifyCodeInCodeSystem(vs, system, code, display);
357    else {
358      ValueSetExpansionOutcome vse = expansionCache.getExpander().expand(vs);
359      if (vse.getValueset() != null)
360        return verifyCodeExternal(vs, new Coding().setSystem(system).setCode(code).setDisplay(display), false);
361      else
362        return verifyCodeInExpansion(vse.getValueset(), system, code, display);
363    }
364  }
365
366  private ValidationResult verifyCodeInCodeSystem(ValueSet vs, String system, String code, String display) {
367    ConceptDefinitionComponent cc = findCodeInConcept(vs.getCodeSystem().getConcept(), code);
368    if (cc == null)
369      return new ValidationResult(IssueSeverity.ERROR,
370          "Unknown Code " + code + " in " + vs.getCodeSystem().getSystem());
371    if (display == null)
372      return new ValidationResult(cc);
373    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
374    if (cc.hasDisplay()) {
375      b.append(cc.getDisplay());
376      if (display.equalsIgnoreCase(cc.getDisplay()))
377        return new ValidationResult(cc);
378    }
379    for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) {
380      b.append(ds.getValue());
381      if (display.equalsIgnoreCase(ds.getValue()))
382        return new ValidationResult(cc);
383    }
384    return new ValidationResult(IssueSeverity.ERROR,
385        "Display Name for " + code + " must be one of '" + b.toString() + "'");
386  }
387
388  private ValidationResult verifyCodeInExpansion(ValueSet vs, String system, String code, String display) {
389    ValueSetExpansionContainsComponent cc = findCode(vs.getExpansion().getContains(), code);
390    if (cc == null)
391      return new ValidationResult(IssueSeverity.ERROR,
392          "Unknown Code " + code + " in " + vs.getCodeSystem().getSystem());
393    if (display == null)
394      return new ValidationResult(new ConceptDefinitionComponent().setCode(code).setDisplay(cc.getDisplay()));
395    if (cc.hasDisplay()) {
396      if (display.equalsIgnoreCase(cc.getDisplay()))
397        return new ValidationResult(new ConceptDefinitionComponent().setCode(code).setDisplay(cc.getDisplay()));
398      return new ValidationResult(IssueSeverity.ERROR,
399          "Display Name for " + code + " must be '" + cc.getDisplay() + "'");
400    }
401    return null;
402  }
403
404  private ValueSetExpansionContainsComponent findCode(List<ValueSetExpansionContainsComponent> contains, String code) {
405    for (ValueSetExpansionContainsComponent cc : contains) {
406      if (code.equals(cc.getCode()))
407        return cc;
408      ValueSetExpansionContainsComponent c = findCode(cc.getContains(), code);
409      if (c != null)
410        return c;
411    }
412    return null;
413  }
414
415  private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code) {
416    for (ConceptDefinitionComponent cc : concept) {
417      if (code.equals(cc.getCode()))
418        return cc;
419      ConceptDefinitionComponent c = findCodeInConcept(cc.getConcept(), code);
420      if (c != null)
421        return c;
422    }
423    return null;
424  }
425
426  @Override
427  public StructureDefinition fetchTypeDefinition(String typeName) {
428    return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
429  }
430}