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