001package org.hl7.fhir.r5.context;
002
003import java.io.File;
004
005/*
006  Copyright (c) 2011+, HL7, Inc.
007  All rights reserved.
008  
009  Redistribution and use in source and binary forms, with or without modification, 
010  are permitted provided that the following conditions are met:
011    
012   * Redistributions of source code must retain the above copyright notice, this 
013     list of conditions and the following disclaimer.
014   * Redistributions in binary form must reproduce the above copyright notice, 
015     this list of conditions and the following disclaimer in the documentation 
016     and/or other materials provided with the distribution.
017   * Neither the name of HL7 nor the names of its contributors may be used to 
018     endorse or promote products derived from this software without specific 
019     prior written permission.
020  
021  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
022  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
023  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
024  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
025  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
026  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
027  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
028  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
029  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
030  POSSIBILITY OF SUCH DAMAGE.
031  
032 */
033
034
035import java.io.FileNotFoundException;
036import java.io.IOException;
037import java.util.ArrayList;
038import java.util.Collections;
039import java.util.Comparator;
040import java.util.Date;
041import java.util.HashMap;
042import java.util.HashSet;
043import java.util.List;
044import java.util.Locale;
045import java.util.Map;
046import java.util.Set;
047
048import lombok.Getter;
049import org.apache.commons.lang3.StringUtils;
050import org.fhir.ucum.UcumService;
051import org.hl7.fhir.exceptions.DefinitionException;
052import org.hl7.fhir.exceptions.FHIRException;
053import org.hl7.fhir.exceptions.NoTerminologyServiceException;
054import org.hl7.fhir.exceptions.TerminologyServiceException;
055import org.hl7.fhir.r5.conformance.ProfileUtilities;
056import org.hl7.fhir.r5.context.BaseWorkerContext.ResourceProxy;
057import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
058import org.hl7.fhir.r5.context.IWorkerContext.PackageVersion;
059import org.hl7.fhir.r5.context.IWorkerContext.ILoggingService.LogCategory;
060import org.hl7.fhir.r5.context.TerminologyCache.CacheToken;
061import org.hl7.fhir.r5.model.BooleanType;
062import org.hl7.fhir.r5.model.Bundle;
063import org.hl7.fhir.r5.model.CanonicalResource;
064import org.hl7.fhir.r5.model.CanonicalType;
065import org.hl7.fhir.r5.model.CapabilityStatement;
066import org.hl7.fhir.r5.model.CodeSystem;
067import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode;
068import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
069import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
070import org.hl7.fhir.r5.model.CodeableConcept;
071import org.hl7.fhir.r5.model.Coding;
072import org.hl7.fhir.r5.model.ConceptMap;
073import org.hl7.fhir.r5.model.Constants;
074import org.hl7.fhir.r5.model.ElementDefinition;
075import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
076import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
077import org.hl7.fhir.r5.model.Identifier;
078import org.hl7.fhir.r5.model.ImplementationGuide;
079import org.hl7.fhir.r5.model.Library;
080import org.hl7.fhir.r5.model.Measure;
081import org.hl7.fhir.r5.model.NamingSystem;
082import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType;
083import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent;
084import org.hl7.fhir.r5.model.OperationDefinition;
085import org.hl7.fhir.r5.model.OperationOutcome;
086import org.hl7.fhir.r5.model.Parameters;
087import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
088import org.hl7.fhir.r5.model.PlanDefinition;
089import org.hl7.fhir.r5.model.Questionnaire;
090import org.hl7.fhir.r5.model.Reference;
091import org.hl7.fhir.r5.model.Resource;
092import org.hl7.fhir.r5.model.SearchParameter;
093import org.hl7.fhir.r5.model.StringType;
094import org.hl7.fhir.r5.model.StructureDefinition;
095import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
096import org.hl7.fhir.r5.model.StructureMap;
097import org.hl7.fhir.r5.model.TerminologyCapabilities;
098import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent;
099import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesExpansionParameterComponent;
100import org.hl7.fhir.r5.model.UriType;
101import org.hl7.fhir.r5.model.ValueSet;
102import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
103import org.hl7.fhir.r5.model.Bundle.BundleType;
104import org.hl7.fhir.r5.model.Bundle.HTTPVerb;
105import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
106import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
107import org.hl7.fhir.r5.renderers.OperationOutcomeRenderer;
108import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
109import org.hl7.fhir.r5.terminologies.TerminologyClient;
110import org.hl7.fhir.r5.terminologies.ValueSetCheckerSimple;
111import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
112import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
113import org.hl7.fhir.r5.terminologies.ValueSetExpanderSimple;
114import org.hl7.fhir.r5.utils.ToolingExtensions;
115import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
116import org.hl7.fhir.utilities.OIDUtils;
117import org.hl7.fhir.utilities.TimeTracker;
118import org.hl7.fhir.utilities.ToolingClientLogger;
119import org.hl7.fhir.utilities.TranslationServices;
120import org.hl7.fhir.utilities.Utilities;
121import org.hl7.fhir.utilities.VersionUtilities;
122import org.hl7.fhir.utilities.i18n.I18nBase;
123import org.hl7.fhir.utilities.i18n.I18nConstants;
124import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
125import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
126import org.hl7.fhir.utilities.validation.ValidationOptions;
127import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode;
128
129import com.google.gson.JsonObject;
130
131import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
132
133public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext{
134
135  public class ResourceProxy {
136    private Resource resource;
137    private CanonicalResourceProxy proxy;
138
139    public ResourceProxy(Resource resource) {
140      super();
141      this.resource = resource;
142    }
143    public ResourceProxy(CanonicalResourceProxy proxy) {
144      super();
145      this.proxy = proxy;
146    }
147    
148    public Resource getResource() {
149      return resource != null ? resource : proxy.getResource();
150    }
151    
152    public String getUrl() {
153      if (resource == null) {
154        return proxy.getUrl();
155      } else if (resource instanceof CanonicalResource) {
156        return ((CanonicalResource) resource).getUrl(); 
157      } else {
158        return null;
159      }
160    }
161    
162  }
163
164  public class MetadataResourceVersionComparator<T extends CanonicalResource> implements Comparator<T> {
165
166    final private List<T> list;
167
168    public MetadataResourceVersionComparator(List<T> list) {
169      this.list = list;
170    }
171
172    @Override
173    public int compare(T arg1, T arg2) {
174      String v1 = arg1.getVersion();
175      String v2 = arg2.getVersion();
176      if (v1 == null && v2 == null) {
177        return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order
178      } else if (v1 == null) {
179        return -1;
180      } else if (v2 == null) {
181        return 1;
182      } else {
183        String mm1 = VersionUtilities.getMajMin(v1);
184        String mm2 = VersionUtilities.getMajMin(v2);
185        if (mm1 == null || mm2 == null) {
186          return v1.compareTo(v2);
187        } else {
188          return mm1.compareTo(mm2);
189        }
190      }
191    }
192  }
193
194  private Object lock = new Object(); // used as a lock for the data that follows
195  protected String version; // although the internal resources are all R5, the version of FHIR they describe may not be 
196  private String cacheId;
197  private boolean isTxCaching;
198  @Getter
199  private int serverQueryCount = 0;
200  private final Set<String> cached = new HashSet<>();
201  
202  private Map<String, Map<String, ResourceProxy>> allResourcesById = new HashMap<String, Map<String, ResourceProxy>>();
203  // all maps are to the full URI
204  private CanonicalResourceManager<CodeSystem> codeSystems = new CanonicalResourceManager<CodeSystem>(false);
205  private final Set<String> supportedCodeSystems = new HashSet<String>();
206  private final Set<String> unsupportedCodeSystems = new HashSet<String>(); // know that the terminology server doesn't support them
207  private CanonicalResourceManager<ValueSet> valueSets = new CanonicalResourceManager<ValueSet>(false);
208  private CanonicalResourceManager<ConceptMap> maps = new CanonicalResourceManager<ConceptMap>(false);
209  protected CanonicalResourceManager<StructureMap> transforms = new CanonicalResourceManager<StructureMap>(false);
210  private CanonicalResourceManager<StructureDefinition> structures = new CanonicalResourceManager<StructureDefinition>(false);
211  private final CanonicalResourceManager<Measure> measures = new CanonicalResourceManager<Measure>(false);
212  private final CanonicalResourceManager<Library> libraries = new CanonicalResourceManager<Library>(false);
213  private CanonicalResourceManager<ImplementationGuide> guides = new CanonicalResourceManager<ImplementationGuide>(false);
214  private final CanonicalResourceManager<CapabilityStatement> capstmts = new CanonicalResourceManager<CapabilityStatement>(false);
215  private final CanonicalResourceManager<SearchParameter> searchParameters = new CanonicalResourceManager<SearchParameter>(false);
216  private final CanonicalResourceManager<Questionnaire> questionnaires = new CanonicalResourceManager<Questionnaire>(false);
217  private final CanonicalResourceManager<OperationDefinition> operations = new CanonicalResourceManager<OperationDefinition>(false);
218  private final CanonicalResourceManager<PlanDefinition> plans = new CanonicalResourceManager<PlanDefinition>(false);
219  private final CanonicalResourceManager<NamingSystem> systems = new CanonicalResourceManager<NamingSystem>(false);
220  
221  private UcumService ucumService;
222  protected Map<String, byte[]> binaries = new HashMap<String, byte[]>();
223  protected Map<String, String> oidCache = new HashMap<>();
224
225  protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String,ValidationResult>>();
226  protected String tsServer;
227  protected String name;
228  private boolean allowLoadingDuplicates;
229
230  protected TerminologyClient txClient;
231  private final Set<String> codeSystemsUsed = new HashSet<>();
232  protected ToolingClientLogger txLog;
233  private TerminologyCapabilities txcaps;
234  private boolean canRunWithoutTerminology;
235  protected boolean noTerminologyServer;
236  private int expandCodesLimit = 1000;
237  protected ILoggingService logger;
238  protected Parameters expParameters;
239  private TranslationServices translator = new NullTranslator();
240
241  @Getter
242  protected TerminologyCache txCache;
243  protected TimeTracker clock;
244  private boolean tlogging = true;
245  private ICanonicalResourceLocator locator;
246  protected String userAgent;
247  
248  protected BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException {
249    setValidationMessageLanguage(getLocale());
250    clock = new TimeTracker();
251  }
252
253  protected BaseWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException {
254    setValidationMessageLanguage(locale);
255    clock = new TimeTracker();
256  }
257
258  protected BaseWorkerContext(CanonicalResourceManager<CodeSystem> codeSystems, CanonicalResourceManager<ValueSet> valueSets, CanonicalResourceManager<ConceptMap> maps, CanonicalResourceManager<StructureDefinition> profiles,
259      CanonicalResourceManager<ImplementationGuide> guides) throws FileNotFoundException, IOException, FHIRException {
260    this();
261    this.codeSystems = codeSystems;
262    this.valueSets = valueSets;
263    this.maps = maps;
264    this.structures = profiles;
265    this.guides = guides;
266    clock = new TimeTracker();
267  }
268
269  protected void copy(BaseWorkerContext other) {
270    synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet 
271      allResourcesById.putAll(other.allResourcesById);
272      translator = other.translator;
273      codeSystems.copy(other.codeSystems);
274      txcaps = other.txcaps;
275      valueSets.copy(other.valueSets);
276      maps.copy(other.maps);
277      transforms.copy(other.transforms);
278      structures.copy(other.structures);
279      searchParameters.copy(other.searchParameters);
280      plans.copy(other.plans);
281      questionnaires.copy(other.questionnaires);
282      operations.copy(other.operations);
283      systems.copy(other.systems);
284      guides.copy(other.guides);
285      capstmts.copy(other.capstmts);
286      measures.copy(other.measures);
287      libraries.copy(libraries);
288
289      allowLoadingDuplicates = other.allowLoadingDuplicates;
290      tsServer = other.tsServer;
291      name = other.name;
292      txClient = other.txClient;
293      txLog = other.txLog;
294      txcaps = other.txcaps;
295      canRunWithoutTerminology = other.canRunWithoutTerminology;
296      noTerminologyServer = other.noTerminologyServer;
297      if (other.txCache != null)
298        txCache = other.txCache.copy();
299      expandCodesLimit = other.expandCodesLimit;
300      logger = other.logger;
301      expParameters = other.expParameters;
302    }
303  }
304  
305  
306  public void cacheResource(Resource r) throws FHIRException {
307    cacheResourceFromPackage(r, null);  
308  }
309  
310
311  public void registerResourceFromPackage(CanonicalResourceProxy r, PackageVersion packageInfo) throws FHIRException {
312    synchronized (lock) {
313      Map<String, ResourceProxy> map = allResourcesById.get(r.getType());
314      if (map == null) {
315        map = new HashMap<String, ResourceProxy>();
316        allResourcesById.put(r.getType(), map);
317      }
318      if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) {
319        map.put(r.getId(), new ResourceProxy(r));
320      }
321
322      String url = r.getUrl();
323      if (!allowLoadingDuplicates && hasResource(r.getType(), url)) {
324        // spcial workaround for known problems with existing packages
325        if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) {
326          return;
327        }
328        throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url));
329      }
330      switch(r.getType()) {
331      case "StructureDefinition":
332        if ("1.4.0".equals(version)) {
333          StructureDefinition sd = (StructureDefinition) r.getResource();
334          fixOldSD(sd);
335        }
336        structures.register(r, packageInfo);
337        break;
338      case "ValueSet":
339        valueSets.register(r, packageInfo);
340        break;
341      case "CodeSystem":
342        codeSystems.register(r, packageInfo);
343        break;
344      case "ImplementationGuide":
345        guides.register(r, packageInfo);
346        break;
347      case "CapabilityStatement":
348        capstmts.register(r, packageInfo);
349        break;
350      case "Measure":
351        measures.register(r, packageInfo);
352        break;
353      case "Library":
354        libraries.register(r, packageInfo);
355        break;
356      case "SearchParameter":
357        searchParameters.register(r, packageInfo);
358        break;
359      case "PlanDefinition":
360        plans.register(r, packageInfo);
361        break;
362      case "OperationDefinition":
363        operations.register(r, packageInfo);
364        break;
365      case "Questionnaire":
366        questionnaires.register(r, packageInfo);
367        break;
368      case "ConceptMap":
369        maps.register(r, packageInfo);
370        break;
371      case "StructureMap":
372        transforms.register(r, packageInfo);
373        break;
374      case "NamingSystem":
375        systems.register(r, packageInfo);
376        break;
377      }
378    }
379  }
380
381  public void cacheResourceFromPackage(Resource r, PackageVersion packageInfo) throws FHIRException {
382    synchronized (lock) {
383      Map<String, ResourceProxy> map = allResourcesById.get(r.fhirType());
384      if (map == null) {
385        map = new HashMap<String, ResourceProxy>();
386        allResourcesById.put(r.fhirType(), map);
387      }
388      if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) {
389        map.put(r.getId(), new ResourceProxy(r));
390      } else {
391        logger.logDebugMessage(LogCategory.PROGRESS,"Ignore "+r.fhirType()+"/"+r.getId()+" from package "+packageInfo.toString());
392      }
393
394      if (r instanceof CodeSystem || r instanceof NamingSystem) {
395        oidCache.clear();
396      }
397
398      if (r instanceof CanonicalResource) {
399        CanonicalResource m = (CanonicalResource) r;
400        String url = m.getUrl();
401        if (!allowLoadingDuplicates && hasResource(r.getClass(), url)) {
402          // spcial workaround for known problems with existing packages
403          if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) {
404            return;
405          }
406          throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url));
407        }
408        if (r instanceof StructureDefinition) {
409          StructureDefinition sd = (StructureDefinition) m;
410          if ("1.4.0".equals(version)) {
411            fixOldSD(sd);
412          }
413          structures.see(sd, packageInfo);
414        } else if (r instanceof ValueSet) {
415          valueSets.see((ValueSet) m, packageInfo);
416        } else if (r instanceof CodeSystem) {
417          CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) r);
418          codeSystems.see((CodeSystem) m, packageInfo);
419        } else if (r instanceof ImplementationGuide) {
420          guides.see((ImplementationGuide) m, packageInfo);
421        } else if (r instanceof CapabilityStatement) {
422          capstmts.see((CapabilityStatement) m, packageInfo);
423        } else if (r instanceof Measure) {
424          measures.see((Measure) m, packageInfo);
425        } else if (r instanceof Library) {
426          libraries.see((Library) m, packageInfo);        
427        } else if (r instanceof SearchParameter) {
428          searchParameters.see((SearchParameter) m, packageInfo);
429        } else if (r instanceof PlanDefinition) {
430          plans.see((PlanDefinition) m, packageInfo);
431        } else if (r instanceof OperationDefinition) {
432          operations.see((OperationDefinition) m, packageInfo);
433        } else if (r instanceof Questionnaire) {
434          questionnaires.see((Questionnaire) m, packageInfo);
435        } else if (r instanceof ConceptMap) {
436          maps.see((ConceptMap) m, packageInfo);
437        } else if (r instanceof StructureMap) {
438          transforms.see((StructureMap) m, packageInfo);
439        } else if (r instanceof NamingSystem) {
440          systems.see((NamingSystem) m, packageInfo);
441        }
442      }
443    }
444  }
445
446  public void fixOldSD(StructureDefinition sd) {
447    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension") && sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
448      sd.setSnapshot(null);
449    }
450    for (ElementDefinition ed : sd.getDifferential().getElement()) {
451      if (ed.getPath().equals("Extension.url") || ed.getPath().endsWith(".extension.url") ) {
452        ed.setMin(1);
453        if (ed.hasBase()) {
454          ed.getBase().setMin(1);
455        }
456      }
457      if ("extension".equals(ed.getSliceName())) {
458        ed.setSliceName(null);
459      }
460    }
461  }
462
463  /*
464   *  Compare business versions, returning "true" if the candidate newer version is in fact newer than the oldVersion
465   *  Comparison will work for strictly numeric versions as well as multi-level versions separated by ., -, _, : or space
466   *  Failing that, it will do unicode-based character ordering.
467   *  E.g. 1.5.3 < 1.14.3
468   *       2017-3-10 < 2017-12-7
469   *       A3 < T2
470   */
471  private boolean laterVersion(String newVersion, String oldVersion) {
472    // Compare business versions, retur
473    newVersion = newVersion.trim();
474    oldVersion = oldVersion.trim();
475    if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion)) {
476      return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion);
477    } else if (hasDelimiter(newVersion, oldVersion, ".")) {
478      return laterDelimitedVersion(newVersion, oldVersion, "\\.");
479    } else if (hasDelimiter(newVersion, oldVersion, "-")) {
480      return laterDelimitedVersion(newVersion, oldVersion, "\\-");
481    } else if (hasDelimiter(newVersion, oldVersion, "_")) {
482      return laterDelimitedVersion(newVersion, oldVersion, "\\_");
483    } else if (hasDelimiter(newVersion, oldVersion, ":")) {
484      return laterDelimitedVersion(newVersion, oldVersion, "\\:");
485    } else if (hasDelimiter(newVersion, oldVersion, " ")) {
486      return laterDelimitedVersion(newVersion, oldVersion, "\\ ");
487    } else {
488      return newVersion.compareTo(oldVersion) > 0;
489    }
490  }
491  
492  /*
493   * Returns true if both strings include the delimiter and have the same number of occurrences of it
494   */
495  private boolean hasDelimiter(String s1, String s2, String delimiter) {
496    return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length;
497  }
498
499  private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) {
500    String[] newParts = newVersion.split(delimiter);
501    String[] oldParts = oldVersion.split(delimiter);
502    for (int i = 0; i < newParts.length; i++) {
503      if (!newParts[i].equals(oldParts[i])) {
504        return laterVersion(newParts[i], oldParts[i]);
505      }
506    }
507    // This should never happen
508    throw new Error(formatMessage(I18nConstants.DELIMITED_VERSIONS_HAVE_EXACT_MATCH_FOR_DELIMITER____VS_, delimiter, newParts, oldParts));
509  }
510  
511  protected <T extends CanonicalResource> void seeMetadataResource(T r, Map<String, T> map, List<T> list, boolean addId) throws FHIRException {
512//    if (addId)
513    //      map.put(r.getId(), r); // todo: why?
514    list.add(r);
515    if (r.hasUrl()) {
516      // first, this is the correct reosurce for this version (if it has a version)
517      if (r.hasVersion()) {
518        map.put(r.getUrl()+"|"+r.getVersion(), r);
519      }
520      // if we haven't get anything for this url, it's the correct version
521      if (!map.containsKey(r.getUrl())) {
522        map.put(r.getUrl(), r);
523      } else {
524        List<T> rl = new ArrayList<T>();
525        for (T t : list) {
526          if (t.getUrl().equals(r.getUrl()) && !rl.contains(t)) {
527            rl.add(t);
528          }
529        }
530        Collections.sort(rl, new MetadataResourceVersionComparator<T>(list));
531        map.put(r.getUrl(), rl.get(rl.size()-1));
532        T latest = null;
533        for (T t : rl) {
534          if (VersionUtilities.versionsCompatible(t.getVersion(), r.getVersion())) {
535            latest = t;
536          }
537        }
538        if (latest != null) { // might be null if it's not using semver
539          map.put(r.getUrl()+"|"+VersionUtilities.getMajMin(latest.getVersion()), rl.get(rl.size()-1));
540        }
541      }
542    }
543  }  
544
545  @Override
546  public CodeSystem fetchCodeSystem(String system) {
547    if (system == null) {
548      return null;
549    }
550    if (system.contains("|")) {
551      String s = system.substring(0, system.indexOf("|"));
552      String v = system.substring(system.indexOf("|")+1);
553      return fetchCodeSystem(s, v);
554    }
555    CodeSystem cs;
556    synchronized (lock) {
557      cs = codeSystems.get(system);
558    }
559    if (cs == null && locator != null) {
560      locator.findResource(this, system);
561      synchronized (lock) {
562        cs = codeSystems.get(system);
563      }
564    }
565    return cs;
566  } 
567
568  public CodeSystem fetchCodeSystem(String system, String version) {
569    if (version == null) {
570      return fetchCodeSystem(system);
571    }
572    CodeSystem cs;
573    synchronized (lock) {
574      cs = codeSystems.get(system, version);
575    }
576    if (cs == null && locator != null) {
577      locator.findResource(this, system);
578      synchronized (lock) {
579        cs = codeSystems.get(system);
580      }
581    }
582    return cs;
583  } 
584
585  @Override
586  public boolean supportsSystem(String system) throws TerminologyServiceException {
587    synchronized (lock) {
588      if (codeSystems.has(system) && codeSystems.get(system).getContent() != CodeSystemContentMode.NOTPRESENT) {
589        return true;
590      } else if (supportedCodeSystems.contains(system)) {
591        return true;
592      } else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) {
593        return false;
594      } else {
595        if (noTerminologyServer) {
596          return false;
597        }
598        if (txcaps == null) {
599          try {
600            log("Terminology server: Check for supported code systems for "+system);
601            final TerminologyCapabilities capabilityStatement = txCache.hasTerminologyCapabilities() ? txCache.getTerminologyCapabilities() : txClient.getTerminologyCapabilities();
602            txCache.cacheTerminologyCapabilities(capabilityStatement);
603            setTxCaps(capabilityStatement);
604          } catch (Exception e) {
605            if (canRunWithoutTerminology) {
606              noTerminologyServer = true;
607              log("==============!! Running without terminology server !! ==============");
608              if (txClient!=null) {
609                log("txServer = "+txClient.getAddress());
610                log("Error = "+e.getMessage()+"");
611              }
612              log("=====================================================================");
613              return false;
614            } else {
615              e.printStackTrace();
616              throw new TerminologyServiceException(e);
617            }
618          }
619          if (supportedCodeSystems.contains(system)) {
620            return true;
621          }
622        }
623      }
624      return false;
625    }
626  }
627
628  private void log(String message) {
629    if (logger != null) {
630      logger.logMessage(message);
631    } else {
632      System.out.println(message);
633    }
634  }
635
636
637  protected void tlog(String msg) {
638    if (tlogging ) {
639      if (logger != null) {
640        logger.logDebugMessage(LogCategory.TX, msg);
641      } else { 
642        System.out.println("-tx: "+msg);
643      }
644    }
645  }
646
647  // --- expansion support ------------------------------------------------------------------------------------------------------------
648
649  public int getExpandCodesLimit() {
650    return expandCodesLimit;
651  }
652
653  public void setExpandCodesLimit(int expandCodesLimit) {
654    this.expandCodesLimit = expandCodesLimit;
655  }
656
657  @Override
658  public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException {
659    ValueSet vs = null;
660    vs = fetchResource(ValueSet.class, binding.getValueSet());
661    if (vs == null) {
662      throw new FHIRException(formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, binding.getValueSet()));
663    }
664    return expandVS(vs, cacheOk, heirarchical);
665  }
666
667
668  @Override
669  public ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean hierarchical, boolean noInactive) throws TerminologyServiceException {
670    ValueSet vs = new ValueSet();
671    vs.setStatus(PublicationStatus.ACTIVE);
672    vs.setCompose(new ValueSetComposeComponent());
673    vs.getCompose().setInactive(!noInactive);
674    vs.getCompose().getInclude().add(inc);
675    CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
676    ValueSetExpansionOutcome res;
677    res = txCache.getExpansion(cacheToken);
678    if (res != null) {
679      return res;
680    }
681    Parameters p = constructParameters(vs, hierarchical);
682    for (ConceptSetComponent incl : vs.getCompose().getInclude()) {
683      codeSystemsUsed.add(incl.getSystem());
684    }
685    for (ConceptSetComponent incl : vs.getCompose().getExclude()) {
686      codeSystemsUsed.add(incl.getSystem());
687    }
688    
689    if (noTerminologyServer) {
690      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE);
691    }
692    Map<String, String> params = new HashMap<String, String>();
693    params.put("_limit", Integer.toString(expandCodesLimit ));
694    params.put("_incomplete", "true");
695    tlog("$expand on "+txCache.summary(vs));
696    try {
697      ValueSet result = txClient.expandValueset(vs, p, params);
698      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());  
699    } catch (Exception e) {
700      res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
701      if (txLog != null) {
702        res.setTxLink(txLog.getLastId());
703      }
704    }
705    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
706    return res;
707  }
708
709  @Override
710  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) {
711    if (expParameters == null)
712      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
713    Parameters p = expParameters.copy(); 
714    return expandVS(vs, cacheOk, heirarchical, false, p);
715  }
716
717  @Override
718  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk) {
719    if (expParameters == null)
720      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
721    Parameters p = expParameters.copy(); 
722    return expandVS(vs, cacheOk, heirarchical, incompleteOk, p);
723  }
724
725  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn)  {
726    if (pIn == null) {
727      throw new Error(formatMessage(I18nConstants.NO_PARAMETERS_PROVIDED_TO_EXPANDVS));
728    }
729
730    Parameters p = pIn.copy();
731
732    if (vs.hasExpansion()) {
733      return new ValueSetExpansionOutcome(vs.copy());
734    }
735    if (!vs.hasUrl()) {
736      throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL));
737    }
738    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
739      codeSystemsUsed.add(inc.getSystem());
740    }
741    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
742      codeSystemsUsed.add(inc.getSystem());
743    }
744
745    CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
746    ValueSetExpansionOutcome res;
747    if (cacheOk) {
748      res = txCache.getExpansion(cacheToken);
749      if (res != null) {
750        return res;
751      }
752    }
753    p.setParameter("includeDefinition", false);
754    p.setParameter("excludeNested", !hierarchical);
755    if (incompleteOk) {
756      p.setParameter("incomplete-ok", true);      
757    }
758
759    List<String> allErrors = new ArrayList<>();
760    
761    // ok, first we try to expand locally
762    ValueSetExpanderSimple vse = constructValueSetExpanderSimple();
763    try {
764      res = vse.expand(vs, p);
765      allErrors.addAll(vse.getAllErrors());
766      if (res.getValueset() != null) {
767        if (!res.getValueset().hasUrl()) {
768          throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET));
769        }
770        txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
771        return res;
772      }
773    } catch (Exception e) {
774      allErrors.addAll(vse.getAllErrors());
775      e.printStackTrace();
776    }
777    
778    // if that failed, we try to expand on the server
779    if (addDependentResources(p, vs)) {
780      p.addParameter().setName("cache-id").setValue(new StringType(cacheId));              
781    }
782    
783    if (noTerminologyServer) {
784      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, allErrors);
785    }
786    Map<String, String> params = new HashMap<String, String>();
787    params.put("_limit", Integer.toString(expandCodesLimit ));
788    params.put("_incomplete", "true");
789    tlog("$expand on "+txCache.summary(vs));
790    try {
791      ValueSet result = txClient.expandValueset(vs, p, params);
792      if (!result.hasUrl()) {
793        result.setUrl(vs.getUrl());
794      }
795      if (!result.hasUrl()) {
796        throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2));
797      }
798      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());  
799    } catch (Exception e) {
800      res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId());
801    }
802    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
803    return res;
804  }
805
806  private boolean hasTooCostlyExpansion(ValueSet valueset) {
807    return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY);
808  }
809  // --- validate code -------------------------------------------------------------------------------
810  
811  @Override
812  public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display) {
813    assert options != null;
814    Coding c = new Coding(system, version, code, display);
815    return validateCode(options, c, null);
816  }
817
818  @Override
819  public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs) {
820    assert options != null;
821    Coding c = new Coding(system, version, code, display);
822    return validateCode(options, c, vs);
823  }
824
825  @Override
826  public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs) {
827    assert options != null;
828    Coding c = new Coding(null, code, null);
829    return validateCode(options.guessSystem(), c, vs);
830  }
831
832
833  @Override
834  public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
835    if (options == null) {
836      options = ValidationOptions.defaults();
837    }
838    // 1st pass: what is in the cache? 
839    // 2nd pass: What can we do internally 
840    // 3rd pass: hit the server
841    for (CodingValidationRequest t : codes) {
842      t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs) : null);
843      if (t.getCoding().hasSystem()) {
844        codeSystemsUsed.add(t.getCoding().getSystem());
845      }
846      if (txCache != null) { 
847        t.setResult(txCache.getValidation(t.getCacheToken()));
848      }
849    }
850    if (options.isUseClient()) {
851      for (CodingValidationRequest t : codes) {
852        if (!t.hasResult()) {
853          try {
854            ValueSetCheckerSimple vsc = constructValueSetCheckerSimple(options, vs);
855            ValidationResult res = vsc.validateCode(t.getCoding());
856            if (txCache != null) {
857              txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT);
858            }
859            t.setResult(res);
860          } catch (Exception e) {
861          }
862        }
863      }      
864    }  
865
866    for (CodingValidationRequest t : codes) {
867      if (!t.hasResult()) {
868        String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem();
869        if (!options.isUseServer()) {
870         t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS));
871        } else if (unsupportedCodeSystems.contains(codeKey)) {
872          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED));      
873        } else if (noTerminologyServer) {
874          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE));
875        }
876      }
877    }
878    
879    if (expParameters == null)
880      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
881    // for those that that failed, we try to validate on the server
882    Bundle batch = new Bundle();
883    batch.setType(BundleType.BATCH);
884    Set<String> systems = new HashSet<>();
885    for (CodingValidationRequest codingValidationRequest : codes) {
886      if (!codingValidationRequest.hasResult()) {
887        Parameters pIn = constructParameters(options, codingValidationRequest, vs);
888        setTerminologyOptions(options, pIn);
889        BundleEntryComponent be = batch.addEntry();
890        be.setResource(pIn);
891        be.getRequest().setMethod(HTTPVerb.POST);
892        be.getRequest().setUrl("CodeSystem/$validate-code");
893        be.setUserData("source", codingValidationRequest);
894        systems.add(codingValidationRequest.getCoding().getSystem());
895      }
896    }
897    if (batch.getEntry().size() > 0) {
898      tlog("$batch validate for "+batch.getEntry().size()+" codes on systems "+systems.toString());
899      if (txClient == null) {
900        throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
901      }
902      if (txLog != null) {
903        txLog.clearLastId();
904      }
905      Bundle resp = txClient.validateBatch(batch);
906      for (int i = 0; i < batch.getEntry().size(); i++) {
907        CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData("source");
908        BundleEntryComponent r = resp.getEntry().get(i);
909        if (r.getResource() instanceof Parameters) {
910          t.setResult(processValidationResult((Parameters) r.getResource()));
911          if (txCache != null) {
912            txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
913          }
914        } else {
915          t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource())).setTxLink(txLog == null ? null : txLog.getLastId()));          
916        }
917      }
918    }    
919  }
920  
921  private String getResponseText(Resource resource) {
922    if (resource instanceof OperationOutcome) {
923      return OperationOutcomeRenderer.toString((OperationOutcome) resource);
924    }
925    return "Todo";
926  }
927
928  @Override
929  public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) {
930    ValidationContextCarrier ctxt = new ValidationContextCarrier();
931    return validateCode(options, code, vs, ctxt);
932  }
933
934  private final String getCodeKey(Coding code) {
935    return code.hasVersion() ? code.getSystem()+"|"+code.getVersion() : code.getSystem();
936  }
937
938  @Override
939  public ValidationResult validateCode(final ValidationOptions optionsArg, final Coding code, final ValueSet vs, final ValidationContextCarrier ctxt) {
940
941    ValidationOptions options = optionsArg != null ? optionsArg : ValidationOptions.defaults();
942
943    if (code.hasSystem()) {
944      codeSystemsUsed.add(code.getSystem());
945    }
946
947    final CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null;
948    ValidationResult res = null;
949    if (txCache != null) {
950      res = txCache.getValidation(cacheToken);
951    }
952    if (res != null) {
953      updateUnsupportedCodeSystems(res, code, getCodeKey(code));
954      return res;
955    }
956
957    if (options.isUseClient()) {
958      // ok, first we try to validate locally
959      try {
960        ValueSetCheckerSimple vsc = constructValueSetCheckerSimple(options, vs, ctxt);
961        if (!vsc.isServerSide(code.getSystem())) {
962          res = vsc.validateCode(code);
963          if (txCache != null) {
964            txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
965          }
966          return res;
967        }
968      } catch (Exception e) {
969      }
970    }
971    
972    if (!options.isUseServer()) {
973      return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS);
974    }
975    String codeKey = getCodeKey(code);
976    if (unsupportedCodeSystems.contains(codeKey)) {
977      return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, code.getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED);      
978    }
979    
980    // if that failed, we try to validate on the server
981    if (noTerminologyServer) {
982      return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE);
983    }
984    String csumm =  txCache != null ? txCache.summary(code) : null;
985    if (txCache != null) {
986      tlog("$validate "+csumm+" for "+ txCache.summary(vs));
987    } else {
988      tlog("$validate "+csumm+" before cache exists");
989    }
990    try {
991      Parameters pIn = constructParameters(options, code);
992      res = validateOnServer(vs, pIn, options);
993    } catch (Exception e) {
994      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR);
995    }
996    updateUnsupportedCodeSystems(res, code, codeKey);
997    if (txCache != null) { // we never cache unsupported code systems - we always keep trying (but only once per run)
998      txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
999    }
1000    return res;
1001  }
1002
1003  protected ValueSetExpanderSimple constructValueSetExpanderSimple() {
1004    return new ValueSetExpanderSimple(this);
1005  }
1006
1007  protected ValueSetCheckerSimple constructValueSetCheckerSimple( ValidationOptions options,  ValueSet vs,  ValidationContextCarrier ctxt) {
1008    return new ValueSetCheckerSimple(options, vs, this, ctxt);
1009  }
1010
1011  protected ValueSetCheckerSimple constructValueSetCheckerSimple( ValidationOptions options,  ValueSet vs) {
1012    return new ValueSetCheckerSimple(options, vs, this);
1013  }
1014
1015  protected Parameters constructParameters(ValueSet vs, boolean hierarchical) {
1016    Parameters p = expParameters.copy();
1017    p.setParameter("includeDefinition", false);
1018    p.setParameter("excludeNested", !hierarchical);
1019
1020    boolean cached = addDependentResources(p, vs);
1021    if (cached) {
1022      p.addParameter().setName("cache-id").setValue(new StringType(cacheId));
1023    }
1024    return p;
1025  }
1026
1027  protected Parameters constructParameters(ValidationOptions options, Coding coding) {
1028    Parameters pIn = new Parameters();
1029    pIn.addParameter().setName("coding").setValue(coding);
1030    if (options.isGuessSystem()) {
1031      pIn.addParameter().setName("implySystem").setValue(new BooleanType(true));
1032    }
1033    setTerminologyOptions(options, pIn);
1034    return pIn;
1035  }
1036
1037  protected Parameters constructParameters(ValidationOptions options, CodeableConcept codeableConcept) {
1038    Parameters pIn = new Parameters();
1039    pIn.addParameter().setName("codeableConcept").setValue(codeableConcept);
1040    setTerminologyOptions(options, pIn);
1041    return pIn;
1042  }
1043
1044  protected Parameters constructParameters(ValidationOptions options, CodingValidationRequest codingValidationRequest, ValueSet valueSet) {
1045    Parameters pIn = new Parameters();
1046    pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
1047    if (options.isGuessSystem()) {
1048      pIn.addParameter().setName("implySystem").setValue(new BooleanType(true));
1049    }
1050    if (valueSet != null) {
1051      pIn.addParameter().setName("valueSet").setResource(valueSet);
1052    }
1053    pIn.addParameter().setName("profile").setResource(expParameters);
1054    return pIn;
1055  }
1056
1057  private void updateUnsupportedCodeSystems(ValidationResult res, Coding code, String codeKey) {
1058    if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && !code.hasVersion()) {
1059      unsupportedCodeSystems.add(codeKey);
1060    }
1061  }
1062
1063  private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
1064    if (!Utilities.noString(options.getLanguage())) {
1065      pIn.addParameter("displayLanguage", options.getLanguage());
1066    }
1067    if (options.getValueSetMode() != ValueSetMode.ALL_CHECKS) {
1068      pIn.addParameter("valueSetMode", options.getValueSetMode().toString());
1069    }
1070    if (options.versionFlexible()) {
1071      pIn.addParameter("default-to-latest-version", true);     
1072    }
1073  }
1074
1075  @Override
1076  public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) {
1077    CacheToken cacheToken = txCache.generateValidationToken(options, code, vs);
1078    ValidationResult res = txCache.getValidation(cacheToken);
1079    if (res != null) {
1080      return res;
1081    }
1082    for (Coding c : code.getCoding()) {
1083      if (c.hasSystem()) {
1084        codeSystemsUsed.add(c.getSystem());
1085      }
1086    }
1087
1088    if (options.isUseClient()) {
1089      // ok, first we try to validate locally
1090      try {
1091        ValueSetCheckerSimple vsc = constructValueSetCheckerSimple(options, vs);
1092        res = vsc.validateCode(code);
1093        txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
1094        return res;
1095      } catch (Exception e) {
1096        if (e instanceof NoTerminologyServiceException) {
1097          return new ValidationResult(IssueSeverity.ERROR, "No Terminology Service", TerminologyServiceErrorClass.NOSERVICE);
1098        }
1099      }
1100    }
1101
1102    if (!options.isUseServer()) {
1103      return new ValidationResult(IssueSeverity.WARNING, "Unable to validate code without using server", TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS);      
1104    }
1105    
1106    // if that failed, we try to validate on the server
1107    if (noTerminologyServer) {
1108      return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE);
1109    }
1110    tlog("$validate "+txCache.summary(code)+" for "+ txCache.summary(vs));
1111    try {
1112      Parameters pIn = constructParameters(options, code);
1113      res = validateOnServer(vs, pIn, options);
1114    } catch (Exception e) {
1115      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog.getLastId());
1116    }
1117    txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
1118    return res;
1119  }
1120
1121  protected ValidationResult validateOnServer(ValueSet vs, Parameters pin, ValidationOptions options) throws FHIRException {
1122    boolean cache = false;
1123    if (vs != null) {
1124      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1125        codeSystemsUsed.add(inc.getSystem());
1126      }
1127      for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1128        codeSystemsUsed.add(inc.getSystem());
1129      }
1130    }
1131    if (vs != null) {
1132      if (isTxCaching && cacheId != null && vs.getUrl() != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) {
1133        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+vs.getVersion() : "")));        
1134      } else if (options.getVsAsUrl()){
1135        pin.addParameter().setName("url").setValue(new StringType(vs.getUrl()));
1136      } else {
1137        pin.addParameter().setName("valueSet").setResource(vs);
1138        if (vs.getUrl() != null) {
1139          cached.add(vs.getUrl()+"|"+vs.getVersion());
1140        }
1141      }
1142      cache = true;
1143      addDependentResources(pin, vs);
1144    }
1145    if (cache) {
1146      pin.addParameter().setName("cache-id").setValue(new StringType(cacheId));              
1147    }
1148    for (ParametersParameterComponent pp : pin.getParameter()) {
1149      if (pp.getName().equals("profile")) {
1150        throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT));
1151      }
1152    }
1153    if (expParameters == null) {
1154      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1155    }
1156    pin.addParameter().setName("profile").setResource(expParameters);
1157    if (txLog != null) {
1158      txLog.clearLastId();
1159    }
1160    if (txClient == null) {
1161      throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1162    }
1163    Parameters pOut;
1164    if (vs == null) {
1165      pOut = txClient.validateCS(pin);
1166    } else {
1167      pOut = txClient.validateVS(pin);
1168    }
1169    return processValidationResult(pOut);
1170  }
1171
1172  private boolean addDependentResources(Parameters pin, ValueSet vs) {
1173    boolean cache = false;
1174    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1175      cache = addDependentResources(pin, inc) || cache;
1176    }
1177    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1178      cache = addDependentResources(pin, inc) || cache;
1179    }
1180    return cache;
1181  }
1182
1183  private boolean addDependentResources(Parameters pin, ConceptSetComponent inc) {
1184    boolean cache = false;
1185    for (CanonicalType c : inc.getValueSet()) {
1186      ValueSet vs = fetchResource(ValueSet.class, c.getValue());
1187      if (vs != null) {
1188        pin.addParameter().setName("tx-resource").setResource(vs);
1189        if (isTxCaching && cacheId == null || !cached.contains(vs.getVUrl())) {
1190          cached.add(vs.getVUrl());
1191          cache = true;
1192        }
1193        addDependentResources(pin, vs);
1194      }
1195    }
1196    CodeSystem cs = fetchResource(CodeSystem.class, inc.getSystem());
1197    if (cs != null && (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
1198      pin.addParameter().setName("tx-resource").setResource(cs);
1199      if (isTxCaching && cacheId == null || !cached.contains(cs.getVUrl())) {
1200        cached.add(cs.getVUrl());
1201        cache = true;
1202      }
1203      // todo: supplements
1204    }
1205    return cache;
1206  }
1207
1208  public ValidationResult processValidationResult(Parameters pOut) {
1209    boolean ok = false;
1210    String message = "No Message returned";
1211    String display = null;
1212    String system = null;
1213    String code = null;
1214    TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN;
1215    for (ParametersParameterComponent p : pOut.getParameter()) {
1216      if (p.hasValue()) {
1217        if (p.getName().equals("result")) {
1218          ok = ((BooleanType) p.getValue()).getValue().booleanValue();
1219        } else if (p.getName().equals("message")) {
1220          message = ((StringType) p.getValue()).getValue();
1221        } else if (p.getName().equals("display")) {
1222          display = ((StringType) p.getValue()).getValue();
1223        } else if (p.getName().equals("system")) {
1224          system = ((StringType) p.getValue()).getValue();
1225        } else if (p.getName().equals("code")) {
1226          code = ((StringType) p.getValue()).getValue();
1227        } else if (p.getName().equals("cause")) {
1228          try {
1229            IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue());
1230            if (it == IssueType.UNKNOWN) {
1231              err = TerminologyServiceErrorClass.UNKNOWN;
1232            } else if (it == IssueType.NOTFOUND) {
1233              err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
1234            } else if (it == IssueType.NOTSUPPORTED) {
1235              err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED;
1236            } else {
1237              err = null;
1238            }
1239          } catch (FHIRException e) {
1240          }
1241        }
1242      }
1243    }
1244    if (!ok) {
1245      return new ValidationResult(IssueSeverity.ERROR, message+" (from "+txClient.getAddress()+")", err).setTxLink(txLog.getLastId());
1246    } else if (message != null && !message.equals("No Message returned")) { 
1247      return new ValidationResult(IssueSeverity.WARNING, message+" (from "+txClient.getAddress()+")", system, new ConceptDefinitionComponent().setDisplay(display).setCode(code)).setTxLink(txLog.getLastId());
1248    } else if (display != null) {
1249      return new ValidationResult(system, new ConceptDefinitionComponent().setDisplay(display).setCode(code)).setTxLink(txLog.getLastId());
1250    } else {
1251      return new ValidationResult(system, new ConceptDefinitionComponent().setCode(code)).setTxLink(txLog.getLastId());
1252    }
1253  }
1254
1255  // --------------------------------------------------------------------------------------------------------------------------------------------------------
1256  
1257  protected void initTS(String cachePath) throws IOException {
1258    if (cachePath != null && !new File(cachePath).exists()) {
1259      Utilities.createDirectory(cachePath);
1260    }
1261    txCache = new TerminologyCache(lock, cachePath);
1262  }
1263
1264  public void clearTSCache(String url) throws Exception {
1265    txCache.removeCS(url);
1266  }
1267
1268  public void clearTS() {
1269    txCache.clear();
1270  }
1271
1272  
1273  @Override
1274  public List<ConceptMap> findMapsForSource(String url) throws FHIRException {
1275    synchronized (lock) {
1276      List<ConceptMap> res = new ArrayList<ConceptMap>();
1277      for (ConceptMap map : maps.getList()) {
1278        if (((Reference) map.getSource()).getReference().equals(url)) { 
1279          res.add(map);
1280        } 
1281      } 
1282      return res;
1283    }
1284  }
1285
1286  public boolean isCanRunWithoutTerminology() {
1287    return canRunWithoutTerminology;
1288  }
1289
1290  public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) {
1291    this.canRunWithoutTerminology = canRunWithoutTerminology;
1292  }
1293
1294  public void setLogger(ILoggingService logger) {
1295    this.logger = logger;
1296  }
1297
1298  public Parameters getExpansionParameters() {
1299    return expParameters;
1300  }
1301
1302  public void setExpansionProfile(Parameters expParameters) {
1303    this.expParameters = expParameters;
1304  }
1305
1306  @Override
1307  public boolean isNoTerminologyServer() {
1308    return noTerminologyServer;
1309  }
1310
1311  public void setNoTerminologyServer(boolean noTerminologyServer) {
1312    this.noTerminologyServer = noTerminologyServer;
1313  }
1314
1315  public String getName() {
1316    return name;
1317  }
1318
1319  public void setName(String name) {
1320    this.name = name;
1321  }
1322
1323  @Override
1324  public Set<String> getResourceNamesAsSet() {
1325    Set<String> res = new HashSet<String>();
1326    res.addAll(getResourceNames());
1327    return res;
1328  }
1329
1330  public boolean isAllowLoadingDuplicates() {
1331    return allowLoadingDuplicates;
1332  }
1333
1334  public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) {
1335    this.allowLoadingDuplicates = allowLoadingDuplicates;
1336  }
1337
1338  @Override
1339  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
1340    return fetchResourceWithException(class_, uri, null);
1341  }
1342  
1343  public <T extends Resource> T fetchResourceWithException(String cls, String uri) throws FHIRException {
1344    return fetchResourceWithException(cls, uri, null);
1345  }
1346  
1347  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, CanonicalResource source) throws FHIRException {
1348    return fetchResourceWithException(class_, uri, null, source);
1349  }
1350  
1351  @SuppressWarnings("unchecked")
1352  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, String version, CanonicalResource source) throws FHIRException {
1353    if (uri == null) {
1354      return null;
1355    }
1356   
1357    if (class_ == StructureDefinition.class) {
1358      uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs());
1359    }
1360    synchronized (lock) {
1361
1362      if (uri.contains("|")) {
1363        version = uri.substring(uri.lastIndexOf("|")+1);
1364        uri = uri.substring(0, uri.lastIndexOf("|"));
1365      }
1366      if (uri.contains("#")) {
1367        uri = uri.substring(0, uri.indexOf("#"));
1368      } 
1369      if (class_ == Resource.class || class_ == null) {
1370        if (structures.has(uri)) {
1371          return (T) structures.get(uri, version);
1372        }        
1373        if (guides.has(uri)) {
1374          return (T) guides.get(uri, version);
1375        } 
1376        if (capstmts.has(uri)) {
1377          return (T) capstmts.get(uri, version);
1378        } 
1379        if (measures.has(uri)) {
1380          return (T) measures.get(uri, version);
1381        } 
1382        if (libraries.has(uri)) {
1383          return (T) libraries.get(uri, version);
1384        } 
1385        if (valueSets.has(uri)) {
1386          return (T) valueSets.get(uri, version);
1387        } 
1388        if (codeSystems.has(uri)) {
1389          return (T) codeSystems.get(uri, version);
1390        } 
1391        if (operations.has(uri)) {
1392          return (T) operations.get(uri, version);
1393        } 
1394        if (searchParameters.has(uri)) {
1395          return (T) searchParameters.get(uri, version);
1396        } 
1397        if (plans.has(uri)) {
1398          return (T) plans.get(uri, version);
1399        } 
1400        if (maps.has(uri)) {
1401          return (T) maps.get(uri, version);
1402        } 
1403        if (transforms.has(uri)) {
1404          return (T) transforms.get(uri, version);
1405        } 
1406        if (questionnaires.has(uri)) {
1407          return (T) questionnaires.get(uri, version);
1408        } 
1409        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
1410          return null;
1411        }
1412
1413        // it might be a special URL.
1414        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
1415          Resource res = null; // findTxValueSet(uri);
1416          if (res != null) {
1417            return (T) res;
1418          }
1419        }
1420        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
1421          for (ResourceProxy r : rt.values()) {
1422            if (uri.equals(r.getUrl())) {
1423              if (version == null || version == r.getResource().getMeta().getVersionId()) {
1424                return (T) r.getResource();
1425              }
1426            }
1427          }            
1428        }
1429        return null;      
1430      } else if (class_ == ImplementationGuide.class) {
1431        return (T) guides.get(uri, version);
1432      } else if (class_ == CapabilityStatement.class) {
1433        return (T) capstmts.get(uri, version);
1434      } else if (class_ == Measure.class) {
1435        return (T) measures.get(uri, version);
1436      } else if (class_ == Library.class) {
1437        return (T) libraries.get(uri, version);
1438      } else if (class_ == StructureDefinition.class) {
1439        return (T) structures.get(uri, version);
1440      } else if (class_ == StructureMap.class) {
1441        return (T) transforms.get(uri, version);
1442      } else if (class_ == ValueSet.class) {
1443        return (T) valueSets.get(uri, version);
1444      } else if (class_ == CodeSystem.class) {
1445        return (T) codeSystems.get(uri, version);
1446      } else if (class_ == ConceptMap.class) {
1447        return (T) maps.get(uri, version);
1448      } else if (class_ == PlanDefinition.class) {
1449        return (T) plans.get(uri, version);
1450      } else if (class_ == OperationDefinition.class) {
1451        OperationDefinition od = operations.get(uri, version);
1452        return (T) od;
1453      } else if (class_ == Questionnaire.class) {
1454        return (T) questionnaires.get(uri, version);
1455      } else if (class_ == SearchParameter.class) {
1456        SearchParameter res = searchParameters.get(uri, version);
1457        return (T) res;
1458      }
1459      if (class_ == CodeSystem.class && codeSystems.has(uri)) { 
1460        return (T) codeSystems.get(uri, version);
1461      }
1462      if (class_ == ValueSet.class && valueSets.has(uri)) {
1463        return (T) valueSets.get(uri, version);
1464      } 
1465      
1466      if (class_ == Questionnaire.class) {
1467        return (T) questionnaires.get(uri, version);
1468      } 
1469      if (supportedCodeSystems.contains(uri)) {
1470        return null;
1471      } 
1472      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
1473    }
1474  }
1475
1476  public PackageVersion getPackageForUrl(String uri) {
1477    if (uri == null) {
1478      return null;
1479    }
1480    uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs());
1481
1482    synchronized (lock) {
1483
1484      String version = null;
1485      if (uri.contains("|")) {
1486        version = uri.substring(uri.lastIndexOf("|")+1);
1487        uri = uri.substring(0, uri.lastIndexOf("|"));
1488      }
1489      if (uri.contains("#")) {
1490        uri = uri.substring(0, uri.indexOf("#"));
1491      } 
1492      if (structures.has(uri)) {
1493        return structures.getPackageInfo(uri, version);
1494      }        
1495      if (guides.has(uri)) {
1496        return guides.getPackageInfo(uri, version);
1497      } 
1498      if (capstmts.has(uri)) {
1499        return capstmts.getPackageInfo(uri, version);
1500      } 
1501      if (measures.has(uri)) {
1502        return measures.getPackageInfo(uri, version);
1503      } 
1504      if (libraries.has(uri)) {
1505        return libraries.getPackageInfo(uri, version);
1506      } 
1507      if (valueSets.has(uri)) {
1508        return valueSets.getPackageInfo(uri, version);
1509      } 
1510      if (codeSystems.has(uri)) {
1511        return codeSystems.getPackageInfo(uri, version);
1512      } 
1513      if (operations.has(uri)) {
1514        return operations.getPackageInfo(uri, version);
1515      } 
1516      if (searchParameters.has(uri)) {
1517        return searchParameters.getPackageInfo(uri, version);
1518      } 
1519      if (plans.has(uri)) {
1520        return plans.getPackageInfo(uri, version);
1521      } 
1522      if (maps.has(uri)) {
1523        return maps.getPackageInfo(uri, version);
1524      } 
1525      if (transforms.has(uri)) {
1526        return transforms.getPackageInfo(uri, version);
1527      } 
1528      if (questionnaires.has(uri)) {
1529        return questionnaires.getPackageInfo(uri, version);
1530      }         
1531      return null;
1532    }
1533  }
1534  
1535  @SuppressWarnings("unchecked")
1536  public <T extends Resource> T fetchResourceWithException(String cls, String uri, CanonicalResource source) throws FHIRException {
1537    if (uri == null) {
1538      return null;
1539    }
1540   
1541    if ("StructureDefinition".equals(cls)) {
1542      uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs());
1543    }
1544    synchronized (lock) {
1545
1546      String version = null;
1547      if (uri.contains("|")) {
1548        version = uri.substring(uri.lastIndexOf("|")+1);
1549        uri = uri.substring(0, uri.lastIndexOf("|"));
1550      }
1551      if (uri.contains("#")) {
1552        uri = uri.substring(0, uri.indexOf("#"));
1553      } 
1554      if (cls == null || "Resource".equals(cls)) {
1555        if (structures.has(uri)) {
1556          return (T) structures.get(uri, version);
1557        } 
1558        if (guides.has(uri)) {
1559          return (T) guides.get(uri, version);
1560        } 
1561        if (capstmts.has(uri)) {
1562          return (T) capstmts.get(uri, version);
1563        } 
1564        if (measures.has(uri)) {
1565          return (T) measures.get(uri, version);
1566        } 
1567        if (libraries.has(uri)) {
1568          return (T) libraries.get(uri, version);
1569        } 
1570        if (valueSets.has(uri)) {
1571          return (T) valueSets.get(uri, version);
1572        } 
1573        if (codeSystems.has(uri)) {
1574          return (T) codeSystems.get(uri, version);
1575        } 
1576        if (operations.has(uri)) {
1577          return (T) operations.get(uri, version);
1578        } 
1579        if (searchParameters.has(uri)) {
1580          return (T) searchParameters.get(uri, version);
1581        } 
1582        if (plans.has(uri)) {
1583          return (T) plans.get(uri, version);
1584        } 
1585        if (maps.has(uri)) {
1586          return (T) maps.get(uri, version);
1587        } 
1588        if (transforms.has(uri)) {
1589          return (T) transforms.get(uri, version);
1590        } 
1591        if (questionnaires.has(uri)) {
1592          return (T) questionnaires.get(uri, version);
1593        } 
1594        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
1595          for (ResourceProxy r : rt.values()) {
1596            if (uri.equals(r.getUrl())) {
1597              return (T) r.getResource();
1598            }
1599          }            
1600        }
1601      } else if ("ImplementationGuide".equals(cls)) {
1602        return (T) guides.get(uri, version);
1603      } else if ("CapabilityStatement".equals(cls)) {
1604        return (T) capstmts.get(uri, version);
1605      } else if ("Measure".equals(cls)) {
1606        return (T) measures.get(uri, version);
1607      } else if ("Library".equals(cls)) {
1608        return (T) libraries.get(uri, version);
1609      } else if ("StructureDefinition".equals(cls)) {
1610        return (T) structures.get(uri, version);
1611      } else if ("StructureMap".equals(cls)) {
1612        return (T) transforms.get(uri, version);
1613      } else if ("ValueSet".equals(cls)) {
1614        return (T) valueSets.get(uri, version);
1615      } else if ("CodeSystem".equals(cls)) {
1616        return (T) codeSystems.get(uri, version);
1617      } else if ("ConceptMap".equals(cls)) {
1618        return (T) maps.get(uri, version);
1619      } else if ("PlanDefinition".equals(cls)) {
1620        return (T) plans.get(uri, version);
1621      } else if ("OperationDefinition".equals(cls)) {
1622        OperationDefinition od = operations.get(uri, version);
1623        return (T) od;
1624      } else if ("Questionnaire.class".equals(cls)) {
1625        return (T) questionnaires.get(uri, version);
1626      } else if ("SearchParameter.class".equals(cls)) {
1627        SearchParameter res = searchParameters.get(uri, version);
1628        return (T) res;
1629      }
1630      if ("CodeSystem".equals(cls) && codeSystems.has(uri)) {
1631        return (T) codeSystems.get(uri, version);
1632      } 
1633      if ("ValueSet".equals(cls) && valueSets.has(uri)) {
1634        return (T) valueSets.get(uri, version);
1635      } 
1636      
1637      if ("Questionnaire".equals(cls)) {
1638        return (T) questionnaires.get(uri, version);
1639      } 
1640      if (cls == null) {
1641        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
1642          return null;
1643        } 
1644
1645        // it might be a special URL.
1646        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
1647          Resource res = null; // findTxValueSet(uri);
1648          if (res != null) {
1649            return (T) res;
1650          } 
1651        }
1652        return null;      
1653      }    
1654      if (supportedCodeSystems.contains(uri)) {
1655        return null;
1656      } 
1657      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
1658    }
1659  }
1660
1661  private Set<String> notCanonical = new HashSet<String>();
1662
1663  private String overrideVersionNs;
1664
1665  @Override
1666  public Resource fetchResourceById(String type, String uri) {
1667    synchronized (lock) {
1668      String[] parts = uri.split("\\/");
1669      if (!Utilities.noString(type) && parts.length == 1) {
1670        if (allResourcesById.containsKey(type)) {
1671          return allResourcesById.get(type).get(parts[0]).getResource();
1672        } else {
1673          return null;
1674        }
1675      }
1676      if (parts.length >= 2) {
1677        if (!Utilities.noString(type)) {
1678          if (!type.equals(parts[parts.length-2])) { 
1679            throw new Error(formatMessage(I18nConstants.RESOURCE_TYPE_MISMATCH_FOR___, type, uri));
1680          }
1681        }
1682        return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]).getResource();
1683      } else {
1684        throw new Error(formatMessage(I18nConstants.UNABLE_TO_PROCESS_REQUEST_FOR_RESOURCE_FOR___, type, uri));
1685      }
1686    }
1687  }
1688
1689  public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource source) {
1690    try {
1691      return fetchResourceWithException(class_, uri, source);
1692    } catch (FHIRException e) {
1693      throw new Error(e);
1694    }    
1695  }
1696  
1697  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
1698    try {
1699      return fetchResourceWithException(class_, uri, null);
1700    } catch (FHIRException e) {
1701      throw new Error(e);
1702    }
1703  }
1704  
1705  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
1706    try {
1707      return fetchResourceWithException(class_, uri, version, null);
1708    } catch (FHIRException e) {
1709      throw new Error(e);
1710    }
1711  }
1712  
1713  @Override
1714  public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
1715    try {
1716      return fetchResourceWithException(class_, uri) != null;
1717    } catch (Exception e) {
1718      return false;
1719    }
1720  }
1721
1722  public <T extends Resource> boolean hasResource(String cls, String uri) {
1723    try {
1724      return fetchResourceWithException(cls, uri) != null;
1725    } catch (Exception e) {
1726      return false;
1727    }
1728  }
1729
1730
1731  public TranslationServices translator() {
1732    return translator;
1733  }
1734
1735  public void setTranslator(TranslationServices translator) {
1736    this.translator = translator;
1737  }
1738  
1739  public class NullTranslator implements TranslationServices {
1740
1741    @Override
1742    public String translate(String context, String value, String targetLang) {
1743      return value;
1744    }
1745
1746    @Override
1747    public String translate(String context, String value) {
1748      return value;
1749    }
1750
1751    @Override
1752    public String toStr(float value) {
1753      return null;
1754    }
1755
1756    @Override
1757    public String toStr(Date value) {
1758      return null;
1759    }
1760
1761    @Override
1762    public String translateAndFormat(String contest, String lang, String value, Object... args) {
1763      return String.format(value, args);
1764    }
1765
1766    @Override
1767    public Map<String, String> translations(String value) {
1768      // TODO Auto-generated method stub
1769      return null;
1770    }
1771
1772    @Override
1773    public Set<String> listTranslations(String category) {
1774      // TODO Auto-generated method stub
1775      return null;
1776    }
1777
1778  }
1779  
1780  public void reportStatus(JsonObject json) {
1781    synchronized (lock) {
1782      json.addProperty("codeystem-count", codeSystems.size());
1783      json.addProperty("valueset-count", valueSets.size());
1784      json.addProperty("conceptmap-count", maps.size());
1785      json.addProperty("transforms-count", transforms.size());
1786      json.addProperty("structures-count", structures.size());
1787      json.addProperty("guides-count", guides.size());
1788      json.addProperty("statements-count", capstmts.size());
1789      json.addProperty("measures-count", measures.size());
1790      json.addProperty("libraries-count", libraries.size());
1791    }
1792  }
1793
1794
1795  public void dropResource(Resource r) throws FHIRException {
1796    dropResource(r.fhirType(), r.getId());   
1797  }
1798
1799  public void dropResource(String fhirType, String id) {
1800    synchronized (lock) {
1801
1802      Map<String, ResourceProxy> map = allResourcesById.get(fhirType);
1803      if (map == null) {
1804        map = new HashMap<String, ResourceProxy>();
1805        allResourcesById.put(fhirType, map);
1806      }
1807      if (map.containsKey(id)) {
1808        map.remove(id); // this is a challenge because we might have more than one resource with this id (different versions)
1809      }
1810
1811      if (fhirType.equals("StructureDefinition")) {
1812        structures.drop(id);
1813      } else if (fhirType.equals("ImplementationGuide")) {
1814        guides.drop(id);
1815      } else if (fhirType.equals("CapabilityStatement")) {
1816        capstmts.drop(id);
1817      } else if (fhirType.equals("Measure")) {
1818        measures.drop(id);
1819      } else if (fhirType.equals("Library")) {
1820        libraries.drop(id);
1821      } else if (fhirType.equals("ValueSet")) {
1822        valueSets.drop(id);
1823      } else if (fhirType.equals("CodeSystem")) {
1824        codeSystems.drop(id);
1825      } else if (fhirType.equals("OperationDefinition")) {
1826        operations.drop(id);
1827      } else if (fhirType.equals("Questionnaire")) {
1828        questionnaires.drop(id);
1829      } else if (fhirType.equals("ConceptMap")) {
1830        maps.drop(id);
1831      } else if (fhirType.equals("StructureMap")) {
1832        transforms.drop(id);
1833      } else if (fhirType.equals("NamingSystem")) {
1834        systems.drop(id);
1835      }
1836    }
1837  }
1838
1839  private <T extends CanonicalResource> void dropMetadataResource(Map<String, T> map, String id) {
1840    T res = map.get(id);
1841    if (res != null) {
1842      map.remove(id);
1843      if (map.containsKey(res.getUrl())) {
1844        map.remove(res.getUrl());
1845      }
1846      if (res.getVersion() != null) {
1847        if (map.containsKey(res.getUrl()+"|"+res.getVersion())) {
1848          map.remove(res.getUrl()+"|"+res.getVersion());
1849        }
1850      }
1851    }
1852  }
1853
1854  @Override
1855  public List<CanonicalResource> allConformanceResources() {
1856    synchronized (lock) {
1857      List<CanonicalResource> result = new ArrayList<CanonicalResource>();
1858      structures.listAllM(result);
1859      guides.listAllM(result);
1860      capstmts.listAllM(result);
1861      measures.listAllM(result);
1862      libraries.listAllM(result);
1863      codeSystems.listAllM(result);
1864      valueSets.listAllM(result);
1865      maps.listAllM(result);
1866      transforms.listAllM(result);
1867      plans.listAllM(result);
1868      questionnaires.listAllM(result);
1869      systems.listAllM(result);
1870      return result;
1871    }
1872  }
1873  
1874  public String listSupportedSystems() {
1875    synchronized (lock) {
1876      String sl = null;
1877      for (String s : supportedCodeSystems) {
1878        sl = sl == null ? s : sl + "\r\n" + s;
1879      }
1880      return sl;
1881    }
1882  }
1883
1884
1885  public int totalCount() {
1886    synchronized (lock) {
1887      return valueSets.size() +  maps.size() + structures.size() + transforms.size();
1888    }
1889  }
1890  
1891  public List<ConceptMap> listMaps() {
1892    List<ConceptMap> m = new ArrayList<ConceptMap>();
1893    synchronized (lock) {
1894      maps.listAll(m);
1895    }
1896    return m;
1897  }
1898  
1899  public List<StructureMap> listTransforms() {
1900    List<StructureMap> m = new ArrayList<StructureMap>();
1901    synchronized (lock) {
1902      transforms.listAll(m);    
1903    }
1904    return m;
1905  }
1906  
1907  public StructureMap getTransform(String code) {
1908    synchronized (lock) {
1909      return transforms.get(code);
1910    }
1911  }
1912
1913  public List<StructureDefinition> listStructures() {
1914    List<StructureDefinition> m = new ArrayList<StructureDefinition>();
1915    synchronized (lock) {
1916      structures.listAll(m);    
1917    }
1918    return m;
1919  }
1920
1921  public StructureDefinition getStructure(String code) {
1922    synchronized (lock) {
1923      return structures.get(code);
1924    }
1925  }
1926
1927  @Override
1928  public String oid2Uri(String oid) {
1929    synchronized (lock) {
1930      if (oid != null && oid.startsWith("urn:oid:")) {
1931        oid = oid.substring(8);
1932      }
1933      if (oidCache.containsKey(oid)) {
1934        return oidCache.get(oid);
1935      }
1936
1937      String uri = OIDUtils.getUriForOid(oid);
1938      if (uri != null) {
1939        oidCache.put(oid, uri);
1940        return uri;
1941      }
1942      CodeSystem cs = fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables");
1943      if (cs != null) {
1944        for (ConceptDefinitionComponent cc : cs.getConcept()) {
1945          for (ConceptPropertyComponent cp : cc.getProperty()) {
1946            if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) {
1947              for (ConceptPropertyComponent cp2 : cc.getProperty()) {
1948                if ("v2-cs-uri".equals(cp2.getCode())) {
1949                  oidCache.put(oid, cp2.getValue().primitiveValue());
1950                  return cp2.getValue().primitiveValue();                  
1951                }
1952              }              
1953            }
1954          }
1955        }
1956      }
1957      for (CodeSystem css : codeSystems.getList()) {
1958        if (("urn:oid:"+oid).equals(css.getUrl())) {
1959          oidCache.put(oid, css.getUrl());
1960          return css.getUrl();
1961        }
1962        for (Identifier id : css.getIdentifier()) {
1963          if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) {
1964            oidCache.put(oid, css.getUrl());
1965            return css.getUrl();
1966          }
1967        }
1968      }
1969      for (NamingSystem ns : systems.getList()) {
1970        if (hasOid(ns, oid)) {
1971          uri = getUri(ns);
1972          if (uri != null) {
1973            oidCache.put(oid, null);
1974            return null;
1975          }
1976        }
1977      }
1978    }
1979    oidCache.put(oid, null);
1980    return null;
1981  }
1982  
1983
1984  private String getUri(NamingSystem ns) {
1985    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
1986      if (id.getType() == NamingSystemIdentifierType.URI) {
1987        return id.getValue();
1988      }
1989    }
1990    return null;
1991  }
1992
1993  private boolean hasOid(NamingSystem ns, String oid) {
1994    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
1995      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) {
1996        return true;
1997      }
1998    }
1999    return false;
2000  }
2001
2002  public void cacheVS(JsonObject json, Map<String, ValidationResult> t) {
2003    synchronized (lock) {
2004      validationCache.put(json.get("url").getAsString(), t);
2005    }
2006  }
2007
2008  public SearchParameter getSearchParameter(String code) {
2009    synchronized (lock) {
2010      return searchParameters.get(code);
2011    }
2012  }
2013
2014  @Override
2015  public String getOverrideVersionNs() {
2016    return overrideVersionNs;
2017  }
2018
2019  @Override
2020  public void setOverrideVersionNs(String value) {
2021    overrideVersionNs = value;
2022  }
2023
2024  @Override
2025  public ILoggingService getLogger() {
2026    return logger;
2027  }
2028
2029  @Override
2030  public StructureDefinition fetchTypeDefinition(String typeName) {
2031    if (Utilities.isAbsoluteUrl(typeName)) {
2032      return fetchResource(StructureDefinition.class, typeName);
2033    } else {
2034      return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+typeName);
2035    }
2036  }
2037
2038  public boolean isTlogging() {
2039    return tlogging;
2040  }
2041
2042  public void setTlogging(boolean tlogging) {
2043    this.tlogging = tlogging;
2044  }
2045
2046  public UcumService getUcumService() {
2047    return ucumService;
2048  }
2049
2050  public void setUcumService(UcumService ucumService) {
2051    this.ucumService = ucumService;
2052  }
2053
2054  @Override
2055  public List<StructureDefinition> getStructures() {
2056    List<StructureDefinition> res = new ArrayList<>();
2057    synchronized (lock) { // tricky, because you need to lock this as well, but it's really not in use yet
2058      structures.listAll(res);
2059    }
2060    return res;
2061  }
2062  
2063  public String getLinkForUrl(String corePath, String url) {
2064    if (url == null) {
2065      return null;
2066    }
2067    
2068    if (codeSystems.has(url)) {
2069      return codeSystems.get(url).getUserString("path");
2070    }
2071
2072    if (valueSets.has(url)) {
2073      return valueSets.get(url).getUserString("path");
2074    }
2075
2076    if (maps.has(url)) {
2077      return maps.get(url).getUserString("path");
2078    }
2079    
2080    if (transforms.has(url)) {
2081      return transforms.get(url).getUserString("path");
2082    }
2083    
2084    if (structures.has(url)) {
2085      return structures.get(url).getUserString("path");
2086    }
2087    
2088    if (guides.has(url)) {
2089      return guides.get(url).getUserString("path");
2090    }
2091    
2092    if (capstmts.has(url)) {
2093      return capstmts.get(url).getUserString("path");
2094    }
2095    
2096    if (measures.has(url)) {
2097      return measures.get(url).getUserString("path");
2098    }
2099
2100    if (libraries.has(url)) {
2101      return libraries.get(url).getUserString("path");
2102    }
2103
2104    if (searchParameters.has(url)) {
2105      return searchParameters.get(url).getUserString("path");
2106    }
2107        
2108    if (questionnaires.has(url)) {
2109      return questionnaires.get(url).getUserString("path");
2110    }
2111
2112    if (operations.has(url)) {
2113      return operations.get(url).getUserString("path");
2114    }
2115    
2116    if (plans.has(url)) {
2117      return plans.get(url).getUserString("path");
2118    }
2119
2120    if (url.equals("http://loinc.org")) {
2121      return corePath+"loinc.html";
2122    }
2123    if (url.equals("http://unitsofmeasure.org")) {
2124      return corePath+"ucum.html";
2125    } 
2126    if (url.equals("http://snomed.info/sct")) {
2127      return corePath+"snomed.html";
2128    } 
2129    return null;
2130  }
2131
2132  public List<ImplementationGuide> allImplementationGuides() {
2133    List<ImplementationGuide> res = new ArrayList<>();
2134    guides.listAll(res);
2135    return res;
2136  }
2137
2138  @Override
2139  public Map<String, byte[]> getBinaries() {
2140    return binaries;
2141  }
2142  
2143  public void finishLoading() {
2144    for (StructureDefinition sd : listStructures()) {
2145      try {
2146        if (sd.getSnapshot().isEmpty()) { 
2147          generateSnapshot(sd);
2148//          new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
2149        }
2150      } catch (Exception e) {
2151//        System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
2152      }
2153    }  
2154  }
2155
2156  protected String tail(String url) {
2157    if (Utilities.noString(url)) {
2158      return "noname";
2159    }
2160    if (url.contains("/")) {
2161      return url.substring(url.lastIndexOf("/")+1);
2162    }
2163    return url;
2164  }
2165  
2166  public int getClientRetryCount() {
2167    return txClient == null ? 0 : txClient.getRetryCount();
2168  }
2169  
2170  public IWorkerContext setClientRetryCount(int value) {
2171    if (txClient != null) {
2172      txClient.setRetryCount(value);
2173    }
2174    return this;
2175  }
2176
2177  public TerminologyClient getTxClient() {
2178    return txClient;
2179  }
2180
2181  public String getCacheId() {
2182    return cacheId;
2183  }
2184
2185  public void setCacheId(String cacheId) {
2186    this.cacheId = cacheId;
2187  }
2188
2189  public TerminologyCapabilities getTxCaps() {
2190    return txcaps;
2191  }
2192
2193  public void setTxCaps(TerminologyCapabilities txCaps) {
2194    this.txcaps = txCaps;
2195    if (txCaps != null) {
2196      for (TerminologyCapabilitiesExpansionParameterComponent t : txcaps.getExpansion().getParameter()) {
2197        if ("cache-id".equals(t.getName())) {
2198          isTxCaching = true;
2199        }
2200      }
2201      for (TerminologyCapabilitiesCodeSystemComponent tccs : txcaps.getCodeSystem()) {
2202        supportedCodeSystems.add(tccs.getUri());
2203      }
2204    }
2205  }
2206
2207  public TimeTracker clock() {
2208    return clock;
2209  }
2210 
2211
2212  public int countAllCaches() {
2213    return codeSystems.size() + valueSets.size() + maps.size() + transforms.size() + structures.size() + measures.size() + libraries.size() + 
2214        guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() + systems.size();
2215  }
2216
2217  public Set<String> getCodeSystemsUsed() {
2218    return codeSystemsUsed ;
2219  }
2220 
2221  public String getSpecUrl() {
2222    String v = getVersion();
2223    switch (VersionUtilities.getMajMin(v)) {
2224    case "1.0" : return "http://hl7.org/fhir/DSTU1";
2225    case "1.4" : return "http://hl7.org/fhir/DSTU2";
2226    case "3.0" : return "http://hl7.org/fhir/STU3";
2227    case "4.0" : return "http://hl7.org/fhir/R4";
2228    case "4.5" : return "http://build.fhir.org";
2229    case "5.0" : return "http://build.fhir.org";
2230    default:
2231      return "http://hl7.org/fhir";
2232    }
2233  }
2234
2235  public ICanonicalResourceLocator getLocator() {
2236    return locator;
2237  }
2238
2239  public void setLocator(ICanonicalResourceLocator locator) {
2240    this.locator = locator;
2241  }
2242
2243  public String getUserAgent() {
2244    return userAgent;
2245  }
2246
2247  protected void setUserAgent(String userAgent) {
2248    this.userAgent = userAgent;
2249    if (txClient != null)
2250      txClient.setUserAgent(userAgent);
2251  }
2252
2253}