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