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.sql.Connection;
038import java.sql.DriverManager;
039import java.sql.PreparedStatement;
040import java.sql.ResultSet;
041import java.util.ArrayList;
042import java.util.Collections;
043import java.util.Comparator;
044import java.util.HashMap;
045import java.util.HashSet;
046import java.util.List;
047import java.util.Locale;
048import java.util.Map;
049import java.util.Set;
050import java.util.UUID;
051
052import lombok.Getter;
053import org.apache.commons.lang3.StringUtils;
054import org.fhir.ucum.UcumService;
055import org.hl7.fhir.exceptions.DefinitionException;
056import org.hl7.fhir.exceptions.FHIRException;
057import org.hl7.fhir.exceptions.TerminologyServiceException;
058import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
059import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
060import org.hl7.fhir.r5.context.ILoggingService.LogCategory;
061import org.hl7.fhir.r5.model.ActorDefinition;
062import org.hl7.fhir.r5.model.BooleanType;
063import org.hl7.fhir.r5.model.Bundle;
064import org.hl7.fhir.r5.model.CanonicalResource;
065import org.hl7.fhir.r5.model.CanonicalType;
066import org.hl7.fhir.r5.model.CapabilityStatement;
067import org.hl7.fhir.r5.model.CodeSystem;
068import org.hl7.fhir.r5.model.CodeType;
069import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
070import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
071import org.hl7.fhir.r5.model.CodeableConcept;
072import org.hl7.fhir.r5.model.Coding;
073import org.hl7.fhir.r5.model.ConceptMap;
074import org.hl7.fhir.r5.model.Constants;
075import org.hl7.fhir.r5.model.DomainResource;
076import org.hl7.fhir.r5.model.ElementDefinition;
077import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
078import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
079import org.hl7.fhir.r5.model.Extension;
080import org.hl7.fhir.r5.model.IdType;
081import org.hl7.fhir.r5.model.Identifier;
082import org.hl7.fhir.r5.model.IntegerType;
083import org.hl7.fhir.r5.model.ImplementationGuide;
084import org.hl7.fhir.r5.model.Library;
085import org.hl7.fhir.r5.model.Measure;
086import org.hl7.fhir.r5.model.NamingSystem;
087import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType;
088import org.hl7.fhir.r5.model.NamingSystem.NamingSystemType;
089import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent;
090import org.hl7.fhir.r5.model.OperationDefinition;
091import org.hl7.fhir.r5.model.OperationOutcome;
092import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
093import org.hl7.fhir.r5.model.PackageInformation;
094import org.hl7.fhir.r5.model.Parameters;
095import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
096import org.hl7.fhir.r5.model.PlanDefinition;
097import org.hl7.fhir.r5.model.PrimitiveType;
098import org.hl7.fhir.r5.model.Questionnaire;
099import org.hl7.fhir.r5.model.Requirements;
100import org.hl7.fhir.r5.model.Resource;
101import org.hl7.fhir.r5.model.SearchParameter;
102import org.hl7.fhir.r5.model.StringType;
103import org.hl7.fhir.r5.model.StructureDefinition;
104import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
105import org.hl7.fhir.r5.model.StructureMap;
106
107import org.hl7.fhir.r5.model.UriType;
108import org.hl7.fhir.r5.model.UrlType;
109import org.hl7.fhir.r5.model.ValueSet;
110import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
111import org.hl7.fhir.r5.model.Bundle.BundleType;
112import org.hl7.fhir.r5.model.Bundle.HTTPVerb;
113import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
114import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
115import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
116import org.hl7.fhir.r5.profilemodel.PEBuilder;
117import org.hl7.fhir.r5.renderers.OperationOutcomeRenderer;
118import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
119import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander;
120import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
121import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
122import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
123import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
124import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
125import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
126import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
127import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.CacheToken;
128import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedCodeSystem;
129import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
130import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
131import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
132import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
133import org.hl7.fhir.r5.terminologies.client.TerminologyClientManager;
134import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5;
135import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext;
136import org.hl7.fhir.r5.utils.PackageHackerR5;
137import org.hl7.fhir.r5.utils.ResourceUtilities;
138import org.hl7.fhir.r5.utils.ToolingExtensions;
139import org.hl7.fhir.r5.utils.UserDataNames;
140import org.hl7.fhir.r5.utils.client.EFhirClientException;
141import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
142import org.hl7.fhir.utilities.*;
143import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
144import org.hl7.fhir.utilities.i18n.I18nBase;
145import org.hl7.fhir.utilities.i18n.I18nConstants;
146import org.hl7.fhir.utilities.i18n.subtag.LanguageSubtagRegistry;
147import org.hl7.fhir.utilities.i18n.subtag.LanguageSubtagRegistryLoader;
148import org.hl7.fhir.utilities.npm.NpmPackage;
149import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
150import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
151import org.hl7.fhir.utilities.validation.ValidationOptions;
152
153import com.google.gson.JsonObject;
154
155import javax.annotation.Nonnull;
156
157@MarkedToMoveToAdjunctPackage
158public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext {
159
160  public interface IByteProvider {
161    byte[] bytes() throws IOException;
162  }
163
164  public class BytesProvider implements IByteProvider {
165
166    private byte[] bytes;
167
168    protected BytesProvider(byte[] bytes) {
169      super();
170      this.bytes = bytes;
171    }
172
173    @Override
174    public byte[] bytes() throws IOException {
175      return bytes;
176    }
177
178  }
179
180  public class BytesFromPackageProvider implements IByteProvider {
181
182    private NpmPackage pi;
183    private String name;
184
185    public BytesFromPackageProvider(NpmPackage pi, String name) {
186      this.pi = pi;
187      this.name = name;
188    }
189
190    @Override
191    public byte[] bytes() throws IOException {
192      return FileUtilities.streamToBytes(pi.load("other", name));
193    }
194
195  }
196  
197  public class BytesFromFileProvider implements IByteProvider {
198
199    private String name;
200
201    public BytesFromFileProvider(String name) {
202      this.name = name;
203    }
204
205    @Override
206    public byte[] bytes() throws IOException {
207      return FileUtilities.streamToBytes(ManagedFileAccess.inStream(name));
208    }
209
210  }
211  
212  class OIDSource {
213    private String folder;
214    private Connection db;
215    private String pid;
216    protected OIDSource(String folder, String pid) {
217      super();
218      this.folder = folder;
219      this.pid = pid;
220    }
221    
222  }
223
224  private static final boolean QA_CHECK_REFERENCE_SOURCE = false; // see comments below
225
226  public static class ResourceProxy {
227    private Resource resource;
228    private CanonicalResourceProxy proxy;
229
230    public ResourceProxy(Resource resource) {
231      super();
232      this.resource = resource;
233    }
234    public ResourceProxy(CanonicalResourceProxy proxy) {
235      super();
236      this.proxy = proxy;
237    }
238    
239    public Resource getResource() {
240      return resource != null ? resource : proxy.getResource();
241    }
242      
243    public CanonicalResourceProxy getProxy() {
244      return proxy;
245    }
246    
247    public String getUrl() {
248      if (resource == null) {
249        return proxy.getUrl();
250      } else if (resource instanceof CanonicalResource) {
251        return ((CanonicalResource) resource).getUrl(); 
252      } else {
253        return null;
254      }
255    }
256    
257  }
258
259  public class MetadataResourceVersionComparator<T extends CanonicalResource> implements Comparator<T> {
260
261    final private List<T> list;
262
263    public MetadataResourceVersionComparator(List<T> list) {
264      this.list = list;
265    }
266
267    @Override
268    public int compare(T arg1, T arg2) {
269      String v1 = arg1.getVersion();
270      String v2 = arg2.getVersion();
271      if (v1 == null && v2 == null) {
272        return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order
273      } else if (v1 == null) {
274        return -1;
275      } else if (v2 == null) {
276        return 1;
277      } else {
278        String mm1 = VersionUtilities.getMajMin(v1);
279        String mm2 = VersionUtilities.getMajMin(v2);
280        if (mm1 == null || mm2 == null) {
281          return v1.compareTo(v2);
282        } else {
283          return mm1.compareTo(mm2);
284        }
285      }
286    }
287  }
288
289  private Object lock = new Object(); // used as a lock for the data that follows
290  protected String version; // although the internal resources are all R5, the version of FHIR they describe may not be 
291
292  private boolean minimalMemory = false;
293
294  private Map<String, Map<String, ResourceProxy>> allResourcesById = new HashMap<String, Map<String, ResourceProxy>>();
295  private Map<String, List<ResourceProxy>> allResourcesByUrl = new HashMap<String, List<ResourceProxy>>();
296  
297  // all maps are to the full URI
298  private CanonicalResourceManager<CodeSystem> codeSystems = new CanonicalResourceManager<CodeSystem>(false, minimalMemory);
299  private final Set<String> supportedCodeSystems = new HashSet<String>();
300  private final Set<String> unsupportedCodeSystems = new HashSet<String>(); // know that the terminology server doesn't support them
301  private CanonicalResourceManager<ValueSet> valueSets = new CanonicalResourceManager<ValueSet>(false, minimalMemory);
302  private CanonicalResourceManager<ConceptMap> maps = new CanonicalResourceManager<ConceptMap>(false, minimalMemory);
303  protected CanonicalResourceManager<StructureMap> transforms = new CanonicalResourceManager<StructureMap>(false, minimalMemory);
304  private CanonicalResourceManager<StructureDefinition> structures = new CanonicalResourceManager<StructureDefinition>(false, minimalMemory);
305  private TypeManager typeManager = new TypeManager(structures);
306  private final CanonicalResourceManager<Measure> measures = new CanonicalResourceManager<Measure>(false, minimalMemory);
307  private final CanonicalResourceManager<Library> libraries = new CanonicalResourceManager<Library>(false, minimalMemory);
308  private CanonicalResourceManager<ImplementationGuide> guides = new CanonicalResourceManager<ImplementationGuide>(false, minimalMemory);
309  private final CanonicalResourceManager<CapabilityStatement> capstmts = new CanonicalResourceManager<CapabilityStatement>(false, minimalMemory);
310  private final CanonicalResourceManager<SearchParameter> searchParameters = new CanonicalResourceManager<SearchParameter>(false, minimalMemory);
311  private final CanonicalResourceManager<Questionnaire> questionnaires = new CanonicalResourceManager<Questionnaire>(false, minimalMemory);
312  private final CanonicalResourceManager<OperationDefinition> operations = new CanonicalResourceManager<OperationDefinition>(false, minimalMemory);
313  private final CanonicalResourceManager<PlanDefinition> plans = new CanonicalResourceManager<PlanDefinition>(false, minimalMemory);
314  private final CanonicalResourceManager<ActorDefinition> actors = new CanonicalResourceManager<ActorDefinition>(false, minimalMemory);
315  private final CanonicalResourceManager<Requirements> requirements = new CanonicalResourceManager<Requirements>(false, minimalMemory);
316  private final CanonicalResourceManager<NamingSystem> systems = new CanonicalResourceManager<NamingSystem>(false, minimalMemory);
317  private Map<String, NamingSystem> systemUrlMap;
318
319  private LanguageSubtagRegistry registry;
320  
321  private UcumService ucumService;
322  protected Map<String, IByteProvider> binaries = new HashMap<String, IByteProvider>();
323  protected Map<String, Set<OIDDefinition>> oidCacheManual = new HashMap<>();
324  protected List<OIDSource> oidSources = new ArrayList<>();
325
326  protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String,ValidationResult>>();
327  protected String name;
328  private boolean allowLoadingDuplicates;
329
330  private final Set<String> codeSystemsUsed = new HashSet<>();
331  protected ToolingClientLogger txLog;
332  protected boolean canRunWithoutTerminology;
333  protected boolean noTerminologyServer;
334  private int expandCodesLimit = 1000;
335  protected org.hl7.fhir.r5.context.ILoggingService logger = new SystemOutLoggingService();
336  protected final TerminologyClientManager terminologyClientManager = new TerminologyClientManager(new TerminologyClientR5.TerminologyClientR5Factory(), UUID.randomUUID().toString(), logger);
337  protected Parameters expParameters;
338  private Map<String, PackageInformation> packages = new HashMap<>();
339
340  @Getter
341  protected TerminologyCache txCache = new TerminologyCache(this, null);
342  protected TimeTracker clock;
343  private boolean tlogging = true;
344  private IWorkerContextManager.ICanonicalResourceLocator locator;
345  protected String userAgent;
346
347  protected BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException {
348    setValidationMessageLanguage(getLocale());
349    clock = new TimeTracker();
350    initLang();
351  }
352
353  protected BaseWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException {
354    setValidationMessageLanguage(locale);
355    clock = new TimeTracker();
356    initLang();
357  }
358
359  protected BaseWorkerContext(CanonicalResourceManager<CodeSystem> codeSystems, CanonicalResourceManager<ValueSet> valueSets, CanonicalResourceManager<ConceptMap> maps, CanonicalResourceManager<StructureDefinition> profiles,
360      CanonicalResourceManager<ImplementationGuide> guides) throws FileNotFoundException, IOException, FHIRException {
361    this();
362    this.codeSystems = codeSystems;
363    this.valueSets = valueSets;
364    this.maps = maps;
365    this.structures = profiles;
366    this.typeManager = new TypeManager(structures);
367    this.guides = guides;
368    clock = new TimeTracker();
369    initLang();
370  }
371
372  private void initLang() throws IOException {
373    registry = new LanguageSubtagRegistry();
374    LanguageSubtagRegistryLoader loader = new LanguageSubtagRegistryLoader(registry);
375    loader.loadFromDefaultResource();
376  }
377
378  protected void copy(BaseWorkerContext other) {
379    synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet 
380      allResourcesById.putAll(other.allResourcesById);
381      codeSystems.copy(other.codeSystems);
382      valueSets.copy(other.valueSets);
383      maps.copy(other.maps);
384      transforms.copy(other.transforms);
385      structures.copy(other.structures);
386      typeManager = new TypeManager(structures);
387      // Snapshot generation is not thread safe, so before this copy of can be used by another thread, we create all the
388      // necessary snapshots. This prevent asynchronous snapshot generation for the shared structure definitions.
389      for (String typeName : typeManager.getTypeNames()) {
390        if (typeName != null) {
391          StructureDefinition structureDefinition = typeManager.fetchTypeDefinition(typeName);
392          generateSnapshot(structureDefinition, "6");
393        }
394      }
395      searchParameters.copy(other.searchParameters);
396      plans.copy(other.plans);
397      questionnaires.copy(other.questionnaires);
398      operations.copy(other.operations);
399      systems.copy(other.systems);
400      systemUrlMap = null;
401      guides.copy(other.guides);
402      capstmts.copy(other.capstmts);
403      measures.copy(other.measures);
404      libraries.copy(other.libraries);
405
406      allowLoadingDuplicates = other.allowLoadingDuplicates;
407      name = other.name;
408      txLog = other.txLog;
409      canRunWithoutTerminology = other.canRunWithoutTerminology;
410      noTerminologyServer = other.noTerminologyServer;
411      if (other.txCache != null)
412        txCache = other.txCache; // no copy. for now?
413      expandCodesLimit = other.expandCodesLimit;
414      logger = other.logger;
415      expParameters = other.expParameters != null ? other.expParameters.copy() : null;
416      version = other.version;
417      supportedCodeSystems.addAll(other.supportedCodeSystems);
418      unsupportedCodeSystems.addAll(other.unsupportedCodeSystems);
419      codeSystemsUsed.addAll(other.codeSystemsUsed);
420      ucumService = other.ucumService;
421      binaries.putAll(other.binaries);
422      oidSources.addAll(other.oidSources);
423      oidCacheManual.putAll(other.oidCacheManual);
424      validationCache.putAll(other.validationCache);
425      tlogging = other.tlogging;
426      locator = other.locator;
427      userAgent = other.userAgent;
428      terminologyClientManager.copy(other.terminologyClientManager);
429      cachingAllowed = other.cachingAllowed;
430    }
431  }
432  
433  
434  public void cacheResource(Resource r) throws FHIRException {
435    cacheResourceFromPackage(r, null);  
436  }
437  
438
439  public void registerResourceFromPackage(CanonicalResourceProxy r, PackageInformation packageInfo) throws FHIRException {    
440    PackageHackerR5.fixLoadedResource(r, packageInfo);
441
442    synchronized (lock) {
443      if (packageInfo != null) {
444        packages.put(packageInfo.getVID(), packageInfo);
445      }
446      if (r.getId() != null) {
447        Map<String, ResourceProxy> map = allResourcesById.get(r.getType());
448        if (map == null) {
449          map = new HashMap<String, ResourceProxy>();
450          allResourcesById.put(r.getType(), map);
451        }
452        if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) {
453          map.put(r.getId(), new ResourceProxy(r));
454        }
455      }
456      if (r.getUrl() != null) {
457        List<ResourceProxy> list = allResourcesByUrl.get(r.getUrl());
458        if (list == null) {
459          list = new ArrayList<>();
460          allResourcesByUrl.put(r.getUrl(), list);
461        }
462        list.add(new ResourceProxy(r));
463      }
464
465      String url = r.getUrl();
466      if (!allowLoadingDuplicates && hasResourceVersion(r.getType(), url, r.getVersion()) && !packageInfo.isHTO()) {
467        // special workaround for known problems with existing packages
468        if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) {
469          return;
470        }
471        CanonicalResource ex = fetchResourceWithException(r.getType(), url);
472        throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url, r.getVersion(), ex.getVersion(),
473            ex.fhirType()));
474      }
475      switch(r.getType()) {
476      case "StructureDefinition":
477        if ("1.4.0".equals(version)) {
478          StructureDefinition sd = (StructureDefinition) r.getResource();
479          fixOldSD(sd);
480        }
481        structures.register(r, packageInfo);
482        typeManager.see(r);
483        break;
484      case "ValueSet":
485        valueSets.register(r, packageInfo);
486        break;
487      case "CodeSystem":        
488        codeSystems.register(r, packageInfo);
489        break;
490      case "ImplementationGuide":
491        guides.register(r, packageInfo);
492        break;
493      case "CapabilityStatement":
494        capstmts.register(r, packageInfo);
495        break;
496      case "Measure":
497        measures.register(r, packageInfo);
498        break;
499      case "Library":
500        libraries.register(r, packageInfo);
501        break;
502      case "SearchParameter":
503        searchParameters.register(r, packageInfo);
504        break;
505      case "PlanDefinition":
506        plans.register(r, packageInfo);
507        break;
508      case "OperationDefinition":
509        operations.register(r, packageInfo);
510        break;
511      case "Questionnaire":
512        questionnaires.register(r, packageInfo);
513        break;
514      case "ConceptMap":
515        maps.register(r, packageInfo);
516        break;
517      case "StructureMap":
518        transforms.register(r, packageInfo);
519        break;
520      case "NamingSystem":
521        systems.register(r, packageInfo);
522        break;
523      case "Requirements":
524        requirements.register(r, packageInfo);
525        break;
526      case "ActorDefinition":
527        actors.register(r, packageInfo);
528        break;
529      }
530    }
531  }
532
533  public void cacheResourceFromPackage(Resource r, PackageInformation packageInfo) throws FHIRException {
534 
535    synchronized (lock) {   
536      if (packageInfo != null) {
537        packages.put(packageInfo.getVID(), packageInfo);
538      }
539
540      if (r.getId() != null) {
541        Map<String, ResourceProxy> map = allResourcesById.get(r.fhirType());
542        if (map == null) {
543          map = new HashMap<String, ResourceProxy>();
544          allResourcesById.put(r.fhirType(), map);
545        }
546        if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) {
547          map.put(r.getId(), new ResourceProxy(r));
548        } else {
549          logger.logDebugMessage(LogCategory.PROGRESS,"Ignore "+r.fhirType()+"/"+r.getId()+" from package "+packageInfo.toString());
550        }
551      }
552      if (r instanceof CanonicalResource) {
553        CanonicalResource cr = (CanonicalResource) r;
554        if (cr.getUrl() != null) {
555          List<ResourceProxy> list = allResourcesByUrl.get(cr.getUrl());
556          if (list == null) {
557            list = new ArrayList<>();
558            allResourcesByUrl.put(cr.getUrl(), list);
559          }
560          list.add(new ResourceProxy(r));
561        }
562      }
563
564      if (r instanceof CodeSystem || r instanceof NamingSystem) {
565        String url = null;
566        Set<String> oids = new HashSet<String>();
567        if (r instanceof CodeSystem) {
568          CodeSystem cs = (CodeSystem) r;
569          url = cs.getUrl();
570          for (Identifier id : cs.getIdentifier()) {
571            if (id.hasValue() && id.getValue().startsWith("urn:oid:")) {
572              oids.add(id.getValue().substring(8));           
573            }
574          }
575        }
576        if (r instanceof NamingSystem) {
577          NamingSystem ns = ((NamingSystem) r);
578          if (ns.getKind() == NamingSystemType.CODESYSTEM) {
579            for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
580              if (id.getType() == NamingSystemIdentifierType.URI) {
581                url = id.getValue();
582              }
583              if (id.getType() == NamingSystemIdentifierType.OID) {
584                oids.add(id.getValue());
585              }
586            }
587          }
588        }
589        if (url != null) {
590          for (String s : oids) {
591            if (!oidCacheManual.containsKey(s)) {
592              oidCacheManual.put(s, new HashSet<>());
593            }
594            oidCacheManual.get(s).add(new OIDDefinition(r.fhirType(), s, url, ((CanonicalResource) r).getVersion(), null, null));
595          }
596        }
597      }
598
599      if (r instanceof CanonicalResource) {
600        CanonicalResource m = (CanonicalResource) r;
601        String url = m.getUrl();
602        if (!allowLoadingDuplicates && hasResource(r.getClass(), url)) {
603          // special workaround for known problems with existing packages
604          if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) {
605            return;
606          }
607          CanonicalResource ex = (CanonicalResource) fetchResourceWithException(r.getClass(), url);
608          throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url, ((CanonicalResource) r).getVersion(), ex.getVersion(),
609              ex.fhirType()));
610        }
611        if (r instanceof StructureDefinition) {
612          StructureDefinition sd = (StructureDefinition) m;
613          if ("1.4.0".equals(version)) {
614            fixOldSD(sd);
615          }
616          structures.see(sd, packageInfo);
617          typeManager.see(sd);
618        } else if (r instanceof ValueSet) {
619          valueSets.see((ValueSet) m, packageInfo);
620        } else if (r instanceof CodeSystem) {
621          CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) r);
622          codeSystems.see((CodeSystem) m, packageInfo);
623        } else if (r instanceof ImplementationGuide) {
624          guides.see((ImplementationGuide) m, packageInfo);
625        } else if (r instanceof CapabilityStatement) {
626          capstmts.see((CapabilityStatement) m, packageInfo);
627        } else if (r instanceof Measure) {
628          measures.see((Measure) m, packageInfo);
629        } else if (r instanceof Library) {
630          libraries.see((Library) m, packageInfo);        
631        } else if (r instanceof SearchParameter) {
632          searchParameters.see((SearchParameter) m, packageInfo);
633        } else if (r instanceof PlanDefinition) {
634          plans.see((PlanDefinition) m, packageInfo);
635        } else if (r instanceof OperationDefinition) {
636          operations.see((OperationDefinition) m, packageInfo);
637        } else if (r instanceof Questionnaire) {
638          questionnaires.see((Questionnaire) m, packageInfo);
639        } else if (r instanceof ConceptMap) {
640          maps.see((ConceptMap) m, packageInfo);
641        } else if (r instanceof StructureMap) {
642          transforms.see((StructureMap) m, packageInfo);
643        } else if (r instanceof NamingSystem) {
644          systems.see((NamingSystem) m, packageInfo);
645          systemUrlMap = null;
646        } else if (r instanceof Requirements) {
647          requirements.see((Requirements) m, packageInfo);
648        } else if (r instanceof ActorDefinition) {
649          actors.see((ActorDefinition) m, packageInfo);
650          systemUrlMap = null;
651        }
652      }
653    }
654  }
655
656  public Map<String, NamingSystem> getNSUrlMap() {
657    if (systemUrlMap == null) {
658      systemUrlMap = new HashMap<>();
659      try {
660      List<NamingSystem> nsl = systems.getList();
661      for (NamingSystem ns : nsl) {
662        for (NamingSystemUniqueIdComponent uid : ns.getUniqueId()) {
663          if (uid.getType() == NamingSystemIdentifierType.URI && uid.hasValue()) {
664            systemUrlMap.put(uid.getValue(), ns) ;
665          }
666        }        
667      }
668      } catch (Exception e) {
669        if (!nsFailHasFailed) {
670          e.printStackTrace();
671          nsFailHasFailed  = true;
672        }
673      }
674    }
675    return systemUrlMap;
676  }
677
678  
679  public void fixOldSD(StructureDefinition sd) {
680    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension") && sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
681      sd.setSnapshot(null);
682    }
683    for (ElementDefinition ed : sd.getDifferential().getElement()) {
684      if (ed.getPath().equals("Extension.url") || ed.getPath().endsWith(".extension.url") ) {
685        ed.setMin(1);
686        if (ed.hasBase()) {
687          ed.getBase().setMin(1);
688        }
689      }
690      if ("extension".equals(ed.getSliceName())) {
691        ed.setSliceName(null);
692      }
693    }
694  }
695
696  /*
697   *  Compare business versions, returning "true" if the candidate newer version is in fact newer than the oldVersion
698   *  Comparison will work for strictly numeric versions as well as multi-level versions separated by ., -, _, : or space
699   *  Failing that, it will do unicode-based character ordering.
700   *  E.g. 1.5.3 < 1.14.3
701   *       2017-3-10 < 2017-12-7
702   *       A3 < T2
703   */
704  private boolean laterVersion(String newVersion, String oldVersion) {
705    // Compare business versions, retur
706    newVersion = newVersion.trim();
707    oldVersion = oldVersion.trim();
708    if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion)) {
709      return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion);
710    } else if (hasDelimiter(newVersion, oldVersion, ".")) {
711      return laterDelimitedVersion(newVersion, oldVersion, "\\.");
712    } else if (hasDelimiter(newVersion, oldVersion, "-")) {
713      return laterDelimitedVersion(newVersion, oldVersion, "\\-");
714    } else if (hasDelimiter(newVersion, oldVersion, "_")) {
715      return laterDelimitedVersion(newVersion, oldVersion, "\\_");
716    } else if (hasDelimiter(newVersion, oldVersion, ":")) {
717      return laterDelimitedVersion(newVersion, oldVersion, "\\:");
718    } else if (hasDelimiter(newVersion, oldVersion, " ")) {
719      return laterDelimitedVersion(newVersion, oldVersion, "\\ ");
720    } else {
721      return newVersion.compareTo(oldVersion) > 0;
722    }
723  }
724  
725  /*
726   * Returns true if both strings include the delimiter and have the same number of occurrences of it
727   */
728  private boolean hasDelimiter(String s1, String s2, String delimiter) {
729    return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length;
730  }
731
732  private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) {
733    String[] newParts = newVersion.split(delimiter);
734    String[] oldParts = oldVersion.split(delimiter);
735    for (int i = 0; i < newParts.length; i++) {
736      if (!newParts[i].equals(oldParts[i])) {
737        return laterVersion(newParts[i], oldParts[i]);
738      }
739    }
740    // This should never happen
741    throw new Error(formatMessage(I18nConstants.DELIMITED_VERSIONS_HAVE_EXACT_MATCH_FOR_DELIMITER____VS_, delimiter, newParts, oldParts));
742  }
743  
744  protected <T extends CanonicalResource> void seeMetadataResource(T r, Map<String, T> map, List<T> list, boolean addId) throws FHIRException {
745//    if (addId)
746    //      map.put(r.getId(), r); // todo: why?
747    list.add(r);
748    if (r.hasUrl()) {
749      // first, this is the correct reosurce for this version (if it has a version)
750      if (r.hasVersion()) {
751        map.put(r.getUrl()+"|"+r.getVersion(), r);
752      }
753      // if we haven't get anything for this url, it's the correct version
754      if (!map.containsKey(r.getUrl())) {
755        map.put(r.getUrl(), r);
756      } else {
757        List<T> rl = new ArrayList<T>();
758        for (T t : list) {
759          if (t.getUrl().equals(r.getUrl()) && !rl.contains(t)) {
760            rl.add(t);
761          }
762        }
763        Collections.sort(rl, new MetadataResourceVersionComparator<T>(list));
764        map.put(r.getUrl(), rl.get(rl.size()-1));
765        T latest = null;
766        for (T t : rl) {
767          if (VersionUtilities.versionsCompatible(t.getVersion(), r.getVersion())) {
768            latest = t;
769          }
770        }
771        if (latest != null) { // might be null if it's not using semver
772          map.put(r.getUrl()+"|"+VersionUtilities.getMajMin(latest.getVersion()), rl.get(rl.size()-1));
773        }
774      }
775    }
776  }  
777
778  @Override
779  public CodeSystem fetchCodeSystem(String system, FhirPublication fhirVersion) {
780    return fetchCodeSystem(system);
781  }
782  
783  @Override
784  public CodeSystem fetchCodeSystem(String system) {
785    if (system == null) {
786      return null;
787    }
788    if (system.contains("|")) {
789      String s = system.substring(0, system.indexOf("|"));
790      String v = system.substring(system.indexOf("|")+1);
791      return fetchCodeSystem(s, v);
792    }
793    CodeSystem cs;
794    synchronized (lock) {
795
796      cs = codeSystems.get(system);
797    }
798    if (cs == null && locator != null) {
799      locator.findResource(this, system);
800      synchronized (lock) {
801        cs = codeSystems.get(system);
802      }
803    }
804    return cs;
805  } 
806
807
808  public CodeSystem fetchCodeSystem(String system, String version, FhirPublication fhirVersion) {
809    return fetchCodeSystem(system, version);
810  }
811  
812  public CodeSystem fetchCodeSystem(String system, String version) {
813    if (version == null) {
814      return fetchCodeSystem(system);
815    }
816    CodeSystem cs;
817    synchronized (lock) {
818      cs = codeSystems.get(system, version);
819    }
820    if (cs == null && locator != null) {
821      locator.findResource(this, system);
822      synchronized (lock) {
823        cs = codeSystems.get(system);
824      }
825    }
826    return cs;
827  } 
828  
829
830  public CodeSystem fetchSupplementedCodeSystem(String system, FhirPublication fhirVersion) {
831    return fetchSupplementedCodeSystem(system);  
832  }
833  
834  public CodeSystem fetchSupplementedCodeSystem(String system, String version, FhirPublication fhirVersion) {
835    return fetchSupplementedCodeSystem(system, version);
836  }
837  
838  @Override
839  public CodeSystem fetchSupplementedCodeSystem(String system) {
840    CodeSystem cs = fetchCodeSystem(system);
841    if (cs != null) {
842      List<CodeSystem> supplements = codeSystems.getSupplements(cs);
843      if (supplements.size() > 0) {
844        cs = CodeSystemUtilities.mergeSupplements(cs, supplements);
845      }
846    }
847    return cs;
848  }
849
850  @Override
851  public CodeSystem fetchSupplementedCodeSystem(String system, String version) {
852    CodeSystem cs = fetchCodeSystem(system, version);
853    if (cs != null) {
854      List<CodeSystem> supplements = codeSystems.getSupplements(cs);
855      if (supplements.size() > 0) {
856        cs = CodeSystemUtilities.mergeSupplements(cs, supplements);
857      }
858    }
859    return cs;
860  }
861
862
863  public boolean supportsSystem(String system, FhirPublication fhirVersion) throws TerminologyServiceException {
864    return supportsSystem(system);
865  }
866  
867  @Override
868  public boolean supportsSystem(String system) throws TerminologyServiceException {
869    synchronized (lock) {
870      if (codeSystems.has(system) && codeSystems.get(system).getContent() != CodeSystemContentMode.NOTPRESENT) {
871        return true;
872      } else if (supportedCodeSystems.contains(system)) {
873        return true;
874      } else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) {
875        return false;
876      } else {
877        if (noTerminologyServer) {
878          return false;
879        }
880        if (terminologyClientManager != null) {
881          try {
882            if (terminologyClientManager.supportsSystem(system)) {
883              supportedCodeSystems.add(system);
884            }
885          } catch (Exception e) {
886            if (canRunWithoutTerminology) {
887              noTerminologyServer = true;
888              logger.logMessage("==============!! Running without terminology server !! ==============");
889              if (terminologyClientManager.getMasterClient() != null) {
890                logger.logMessage("txServer = "+ terminologyClientManager.getMasterClient().getId());
891                logger.logMessage("Error = "+e.getMessage()+"");
892              }
893              logger.logMessage("=====================================================================");
894              return false;
895            } else {
896              e.printStackTrace();
897              throw new TerminologyServiceException(e);
898            }
899          }
900          if (supportedCodeSystems.contains(system)) {
901            return true;
902          }
903        }
904      }
905      return false;
906    }
907  }
908
909
910  public boolean isServerSideSystem(String url) {
911    boolean check = supportsSystem(url);
912    return check && supportedCodeSystems.contains(url);
913  }
914
915
916  protected void txLog(String msg) {
917    if (tlogging ) {
918        logger.logDebugMessage(LogCategory.TX, msg);
919    }
920  }
921
922  // --- expansion support ------------------------------------------------------------------------------------------------------------
923
924  public int getExpandCodesLimit() {
925    return expandCodesLimit;
926  }
927
928  public void setExpandCodesLimit(int expandCodesLimit) {
929    this.expandCodesLimit = expandCodesLimit;
930  }
931
932  @Override
933  public ValueSetExpansionOutcome expandVS(Resource src, ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException {
934    ValueSet vs = null;
935    vs = fetchResource(ValueSet.class, binding.getValueSet(), src);
936    if (vs == null) {
937      throw new FHIRException(formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, binding.getValueSet()));
938    }
939    return expandVS(vs, cacheOk, heirarchical);
940  }
941
942
943  @Override
944  public ValueSetExpansionOutcome expandVS(ITerminologyOperationDetails opCtxt, ConceptSetComponent inc, boolean hierarchical, boolean noInactive) throws TerminologyServiceException {
945    ValueSet vs = new ValueSet();
946    vs.setStatus(PublicationStatus.ACTIVE);
947    vs.setCompose(new ValueSetComposeComponent());
948    vs.getCompose().setInactive(!noInactive);
949    vs.getCompose().getInclude().add(inc);
950    CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
951    ValueSetExpansionOutcome res;
952    res = txCache.getExpansion(cacheToken);
953    if (res != null) {
954      return res;
955    }
956    Set<String> systems = findRelevantSystems(vs);
957    TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, true);
958    if (tc == null) {
959      return new ValueSetExpansionOutcome("No server available", TerminologyServiceErrorClass.INTERNAL_ERROR, true);      
960    }
961    Parameters p = constructParameters(opCtxt, tc, vs, hierarchical);
962    for (ConceptSetComponent incl : vs.getCompose().getInclude()) {
963      codeSystemsUsed.add(incl.getSystem());
964    }
965    for (ConceptSetComponent incl : vs.getCompose().getExclude()) {
966      codeSystemsUsed.add(incl.getSystem());
967    }
968    
969    if (noTerminologyServer) {
970      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, false);
971    }
972    p.addParameter("count", expandCodesLimit);
973    p.addParameter("offset", 0);
974    txLog("$expand on "+txCache.summary(vs)+" on "+tc.getAddress());
975    if (addDependentResources(opCtxt, tc, p, vs)) {
976      p.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
977    }
978
979    try {
980      ValueSet result = tc.getClient().expandValueset(vs, p);
981      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
982      if (res != null && res.getValueset() != null) { 
983        res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, tc.getHost());
984      }
985    } catch (Exception e) {
986      res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, true);
987      if (txLog != null) {
988        res.setTxLink(txLog.getLastId());
989      }
990    }
991    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
992    return res;
993  }
994
995  @Override
996  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) {
997    if (expParameters == null)
998      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
999    Parameters p = expParameters.copy(); 
1000    return expandVS(vs, cacheOk, heirarchical, false, p);
1001  }
1002
1003  @Override
1004  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, int count) {
1005    if (expParameters == null)
1006      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
1007    Parameters p = expParameters.copy(); 
1008    p.addParameter("count", count);
1009    return expandVS(vs, cacheOk, heirarchical, false, p);
1010  }
1011
1012  @Override
1013  public ValueSetExpansionOutcome expandVS(String url, boolean cacheOk, boolean hierarchical, int count) {
1014    if (expParameters == null)
1015      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
1016    if (noTerminologyServer) {
1017      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null, false);
1018    }
1019
1020    Parameters p = expParameters.copy(); 
1021    p.addParameter("count", count);
1022    p.addParameter("url", new UriType(url));
1023    p.setParameter("_limit",new IntegerType("10000"));
1024    p.setParameter("_incomplete", new BooleanType("true"));
1025
1026    CacheToken cacheToken = txCache.generateExpandToken(url, hierarchical);
1027    ValueSetExpansionOutcome res;
1028    if (cacheOk) {
1029      res = txCache.getExpansion(cacheToken);
1030      if (res != null) {
1031        return res;
1032      }
1033    }
1034    p.setParameter("excludeNested", !hierarchical);
1035    List<String> allErrors = new ArrayList<>();
1036
1037    p.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
1038    TerminologyClientContext tc = terminologyClientManager.chooseServer(url, true);
1039    try {
1040      if (tc == null) {
1041        throw new FHIRException("Unable to find a server to expand '"+url+"'");
1042      }
1043      txLog("$expand "+url+" on "+tc.getAddress());
1044    
1045      ValueSet result = tc.getClient().expandValueset(null, p);
1046      if (result != null) {
1047        if (!result.hasUrl()) {
1048          result.setUrl(url);
1049        }
1050        if (!result.hasUrl()) {
1051          throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2));
1052        }
1053      }
1054      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); 
1055      if (res != null && res.getValueset() != null) { 
1056        res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, tc.getHost());
1057      } 
1058    } catch (Exception e) {
1059      res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId());
1060    }
1061    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
1062    return res;
1063  }
1064
1065  @Override
1066  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk) {
1067    if (expParameters == null)
1068      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
1069    Parameters p = expParameters.copy(); 
1070    return expandVS(vs, cacheOk, heirarchical, incompleteOk, p);
1071  }
1072
1073  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn)  {
1074    return expandVS(vs, cacheOk, hierarchical, incompleteOk, pIn, false);
1075  }
1076  
1077  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn, boolean noLimits)  {
1078    if (pIn == null) {
1079      throw new Error(formatMessage(I18nConstants.NO_PARAMETERS_PROVIDED_TO_EXPANDVS));
1080    }
1081    if (vs.hasUrl() && (vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-time-units") || vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-distance-units"))) {
1082      return new ValueSetExpansionOutcome("This value set is not expanded correctly at this time (will be fixed in a future version)", TerminologyServiceErrorClass.VALUESET_UNSUPPORTED, false);
1083    }
1084    
1085    Parameters p = pIn.copy();
1086    p.setParameter("_limit",new IntegerType("10000"));
1087    p.setParameter("_incomplete", new BooleanType("true"));
1088    if (vs.hasExpansion()) {
1089      return new ValueSetExpansionOutcome(vs.copy());
1090    }
1091    if (!vs.hasUrl()) {
1092      throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL));
1093    }
1094    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1095      if (inc.hasSystem()) {
1096        codeSystemsUsed.add(inc.getSystem());
1097      }
1098    }
1099    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1100      if (inc.hasSystem()) {
1101        codeSystemsUsed.add(inc.getSystem());
1102      }
1103    }
1104
1105    CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
1106    ValueSetExpansionOutcome res;
1107    if (cacheOk) {
1108      res = txCache.getExpansion(cacheToken);
1109      if (res != null) {
1110        return res;
1111      }
1112    }
1113
1114    if (!noLimits && !p.hasParameter("count")) {
1115      p.addParameter("count", expandCodesLimit);
1116      p.addParameter("offset", 0);
1117    }
1118    p.setParameter("excludeNested", !hierarchical);
1119    if (incompleteOk) {
1120      p.setParameter("incomplete-ok", true);      
1121    }
1122
1123    List<String> allErrors = new ArrayList<>();
1124    
1125    // ok, first we try to expand locally
1126    ValueSetExpander vse = constructValueSetExpanderSimple(new ValidationOptions(vs.getFHIRPublicationVersion()));
1127    res = null;
1128    try {
1129      res = vse.expand(vs, p);
1130      if (res != null && res.getValueset() != null) { 
1131        res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, vse.getSource());
1132      }
1133    } catch (Exception e) {
1134      allErrors.addAll(vse.getAllErrors());
1135      e.printStackTrace();
1136      res = new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, e instanceof EFhirClientException);
1137    }
1138    allErrors.addAll(vse.getAllErrors());
1139    if (res.getValueset() != null) {
1140      if (!res.getValueset().hasUrl()) {
1141        throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET));
1142      }
1143      txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
1144      return res;
1145    }
1146    if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR || isNoTerminologyServer() || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNKNOWN) { // this class is created specifically to say: don't consult the server
1147      return res;
1148    }
1149
1150    // if that failed, we try to expand on the server
1151    if (noTerminologyServer) {
1152      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, allErrors, false);
1153    }
1154
1155    p.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
1156    Set<String> systems = findRelevantSystems(vs);
1157    TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, true);
1158    addDependentResources(null, tc, p, vs);
1159
1160    
1161    txLog("$expand on "+txCache.summary(vs)+" on "+tc.getAddress());
1162    
1163    try {
1164      ValueSet result = tc.getClient().expandValueset(vs, p);
1165      if (result != null) {
1166        if (!result.hasUrl()) {
1167          result.setUrl(vs.getUrl());
1168        }
1169        if (!result.hasUrl()) {
1170          throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2));
1171        }
1172      }
1173      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());  
1174    } catch (Exception e) {
1175      if (res != null && !res.isFromServer()) {
1176        res = new ValueSetExpansionOutcome(res.getError()+" (and "+e.getMessage()+")", res.getErrorClass(), false);
1177      } else {
1178        res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId());
1179      }
1180    }
1181    if (res != null && res.getValueset() != null) {
1182      res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, tc.getHost());
1183    }
1184    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
1185    return res;
1186  }
1187
1188//  private boolean hasTooCostlyExpansion(ValueSet valueset) {
1189//    return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY);
1190//  }
1191  
1192  // --- validate code -------------------------------------------------------------------------------
1193  
1194  @Override
1195  public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display) {
1196    assert options != null;
1197    Coding c = new Coding(system, version, code, display);
1198    return validateCode(options, c, null);
1199  }
1200
1201  @Override
1202  public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs) {
1203    assert options != null;
1204    Coding c = new Coding(system, version, code, display);
1205    ValidationResult ret = validateCode(options, "$", c, vs);
1206    ret.trimPath("$");
1207    return ret;
1208  }
1209
1210  @Override
1211  public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs) {
1212    assert options != null;
1213    Coding c = new Coding(null, code, null);
1214    return validateCode(options.withGuessSystem(), c, vs);
1215  }
1216
1217
1218  @Override
1219  public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
1220    if (options == null) {
1221      options = ValidationOptions.defaults();
1222    }
1223    // 1st pass: what is in the cache? 
1224    // 2nd pass: What can we do internally 
1225    // 3rd pass: hit the server
1226    for (CodingValidationRequest t : codes) {
1227      t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs == null ? t.getVsObj() : vs, expParameters) : null);
1228      if (t.getCoding().hasSystem()) {
1229        codeSystemsUsed.add(t.getCoding().getSystem());
1230      }
1231      if (txCache != null) { 
1232        t.setResult(txCache.getValidation(t.getCacheToken()));
1233      }
1234    }
1235    if (options.isUseClient()) {
1236      for (CodingValidationRequest t : codes) {
1237        if (!t.hasResult()) {
1238          try {
1239            ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs == null ? t.getVsObj() : vs);
1240            vsc.setThrowToServer(options.isUseServer() && terminologyClientManager.hasClient());
1241            ValidationResult res = vsc.validateCode("Coding", t.getCoding());
1242            if (txCache != null) {
1243              txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT);
1244            }
1245            t.setResult(res);
1246          } catch (Exception e) {
1247          }
1248        }
1249      }      
1250    }  
1251
1252    for (CodingValidationRequest t : codes) {
1253      if (!t.hasResult()) {
1254        String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem();
1255        if (!options.isUseServer()) {
1256         t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null));
1257        } else if (unsupportedCodeSystems.contains(codeKey)) {
1258          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));      
1259        } else if (noTerminologyServer) {
1260          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, t.getCoding().getCode(), t.getCoding().getSystem()), TerminologyServiceErrorClass.NOSERVICE, null));
1261        }
1262      }
1263    }
1264    
1265    if (expParameters == null)
1266      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1267    // for those that that failed, we try to validate on the server
1268    Bundle batch = new Bundle();
1269    batch.setType(BundleType.BATCH);
1270    Set<String> systems = findRelevantSystems(vs);
1271    for (CodingValidationRequest codingValidationRequest : codes) {
1272      if (!codingValidationRequest.hasResult()) {
1273        Parameters pIn = constructParameters(options, codingValidationRequest, vs == null ? codingValidationRequest.getVsObj() : vs);
1274        setTerminologyOptions(options, pIn);
1275        BundleEntryComponent be = batch.addEntry();
1276        be.setResource(pIn);
1277        be.getRequest().setMethod(HTTPVerb.POST);
1278        if (vs != null || codingValidationRequest.getVsObj() != null) {
1279          be.getRequest().setUrl("ValueSet/$validate-code");          
1280        } else {
1281          be.getRequest().setUrl("CodeSystem/$validate-code");
1282        }
1283        be.setUserData(UserDataNames.TX_REQUEST, codingValidationRequest);
1284        systems.add(codingValidationRequest.getCoding().getSystem());
1285        findRelevantSystems(systems, codingValidationRequest.getCoding());
1286      }
1287    }
1288    
1289    if (batch.getEntry().size() > 0) {
1290      TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false);
1291      Bundle resp = processBatch(tc, batch, systems);      
1292      for (int i = 0; i < batch.getEntry().size(); i++) {
1293        CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData(UserDataNames.TX_REQUEST);
1294        BundleEntryComponent r = resp.getEntry().get(i);
1295
1296        if (r.getResource() instanceof Parameters) {
1297          t.setResult(processValidationResult((Parameters) r.getResource(), vs != null ? vs.getUrl() : t.getVsObj() != null ? t.getVsObj().getUrl() : null, tc.getAddress()));
1298          if (txCache != null) {
1299            txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
1300          }
1301        } else {
1302          t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource()), null).setTxLink(txLog == null ? null : txLog.getLastId()));          
1303        }
1304      }
1305    }    
1306  }
1307
1308  private Bundle processBatch(TerminologyClientContext tc, Bundle batch, Set<String> systems) {
1309    txLog("$batch validate for "+batch.getEntry().size()+" codes on systems "+systems.toString());
1310    if (terminologyClientManager == null) {
1311      throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1312    }
1313    if (txLog != null) {
1314      txLog.clearLastId();
1315    }
1316    Bundle resp = tc.getClient().validateBatch(batch);
1317    if (resp == null) {
1318      throw new FHIRException(formatMessage(I18nConstants.TX_SERVER_NO_BATCH_RESPONSE));          
1319    }
1320    return resp;
1321  }
1322  
1323  @Override
1324  public void validateCodeBatchByRef(ValidationOptions options, List<? extends CodingValidationRequest> codes, String vsUrl) {
1325    if (options == null) {
1326      options = ValidationOptions.defaults();
1327    }
1328    // 1st pass: what is in the cache? 
1329    // 2nd pass: What can we do internally 
1330    // 3rd pass: hit the server
1331    for (CodingValidationRequest t : codes) {
1332      t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vsUrl, expParameters) : null);
1333      if (t.getCoding().hasSystem()) {
1334        codeSystemsUsed.add(t.getCoding().getSystem());
1335      }
1336      if (txCache != null) { 
1337        t.setResult(txCache.getValidation(t.getCacheToken()));
1338      }
1339    }
1340    ValueSet vs = fetchResource(ValueSet.class, vsUrl);
1341    if (options.isUseClient()) {
1342      if (vs != null) {
1343        for (CodingValidationRequest t : codes) {
1344          if (!t.hasResult()) {
1345            try {
1346              ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs);
1347              vsc.setThrowToServer(options.isUseServer() && terminologyClientManager.hasClient());
1348              ValidationResult res = vsc.validateCode("Coding", t.getCoding());
1349              if (txCache != null) {
1350                txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT);
1351              }
1352              t.setResult(res);
1353            } catch (Exception e) {
1354            }
1355          }
1356        }      
1357      }  
1358    }
1359
1360    for (CodingValidationRequest t : codes) {
1361      if (!t.hasResult()) {
1362        String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem();
1363        if (!options.isUseServer()) {
1364         t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null));
1365        } else if (unsupportedCodeSystems.contains(codeKey)) {
1366          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));      
1367        } else if (noTerminologyServer) {
1368          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, t.getCoding().getCode(), t.getCoding().getSystem()), TerminologyServiceErrorClass.NOSERVICE, null));
1369        }
1370      }
1371    }
1372    
1373    if (expParameters == null)
1374      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1375    // for those that that failed, we try to validate on the server
1376    Bundle batch = new Bundle();
1377    batch.setType(BundleType.BATCH);
1378    Set<String> systems = vs != null ? findRelevantSystems(vs) : new HashSet<>();
1379    for (CodingValidationRequest codingValidationRequest : codes) {
1380      if (!codingValidationRequest.hasResult()) {
1381        Parameters pIn = constructParameters(options, codingValidationRequest, vsUrl);
1382        setTerminologyOptions(options, pIn);
1383        BundleEntryComponent be = batch.addEntry();
1384        be.setResource(pIn);
1385        be.getRequest().setMethod(HTTPVerb.POST);
1386        if (vsUrl != null) {
1387          be.getRequest().setUrl("ValueSet/$validate-code");
1388        } else {
1389          be.getRequest().setUrl("CodeSystem/$validate-code");
1390        }
1391        be.setUserData(UserDataNames.TX_REQUEST, codingValidationRequest);
1392        systems.add(codingValidationRequest.getCoding().getSystem());
1393      }
1394    }
1395    TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false);
1396    
1397    if (batch.getEntry().size() > 0) {
1398      Bundle resp = processBatch(tc, batch, systems);      
1399      for (int i = 0; i < batch.getEntry().size(); i++) {
1400        CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData(UserDataNames.TX_REQUEST);
1401        BundleEntryComponent r = resp.getEntry().get(i);
1402
1403        if (r.getResource() instanceof Parameters) {
1404          t.setResult(processValidationResult((Parameters) r.getResource(), vsUrl, tc.getAddress()));
1405          if (txCache != null) {
1406            txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
1407          }
1408        } else {
1409          t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource()), null).setTxLink(txLog == null ? null : txLog.getLastId()));          
1410        }
1411      }
1412    }    
1413  }
1414  
1415  private String getResponseText(Resource resource) {
1416    if (resource instanceof OperationOutcome) {
1417      return OperationOutcomeRenderer.toString((OperationOutcome) resource);
1418    }
1419    return "Todo";
1420  }
1421
1422  @Override
1423  public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) {
1424    ValidationContextCarrier ctxt = new ValidationContextCarrier();
1425    return validateCode(options, "Coding", code, vs, ctxt);
1426  }
1427  
1428  public ValidationResult validateCode(ValidationOptions options, String path, Coding code, ValueSet vs) {
1429    ValidationContextCarrier ctxt = new ValidationContextCarrier();
1430    return validateCode(options, path, code, vs, ctxt);
1431  }
1432
1433  private final String getCodeKey(Coding code) {
1434    return code.hasVersion() ? code.getSystem()+"|"+code.getVersion() : code.getSystem();
1435  }
1436
1437  @Override
1438  public ValidationResult validateCode(final ValidationOptions optionsArg, final Coding code, final ValueSet vs, final ValidationContextCarrier ctxt) {
1439    return validateCode(optionsArg, "Coding", code, vs, ctxt); 
1440  }
1441  
1442  public ValidationResult validateCode(final ValidationOptions optionsArg, String path, final Coding code, final ValueSet vs, final ValidationContextCarrier ctxt) {
1443  
1444    ValidationOptions options = optionsArg != null ? optionsArg : ValidationOptions.defaults();
1445    
1446    if (code.hasSystem()) {
1447      codeSystemsUsed.add(code.getSystem());
1448    }
1449
1450    final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateValidationToken(options, code, vs, expParameters) : null;
1451    ValidationResult res = null;
1452    if (cachingAllowed && txCache != null) {
1453      res = txCache.getValidation(cacheToken);
1454    }
1455    if (res != null) {
1456      updateUnsupportedCodeSystems(res, code, getCodeKey(code));
1457      return res;
1458    }
1459
1460    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1461    Set<String> unknownSystems = new HashSet<>();
1462    
1463    String localError = null;
1464    String localWarning = null;
1465    TerminologyServiceErrorClass type = TerminologyServiceErrorClass.UNKNOWN;
1466    if (options.isUseClient()) {
1467      // ok, first we try to validate locally
1468      try {
1469        ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs, ctxt);
1470        if (vsc.getOpContext() != null) {
1471          vsc.getOpContext().note("Validate "+code.toString()+" @ "+path+" against "+(vs == null ? "null" : vs.getVersionedUrl()));
1472        }
1473        vsc.setUnknownSystems(unknownSystems);
1474        vsc.setThrowToServer(options.isUseServer() && terminologyClientManager.hasClient());
1475        if (!ValueSetUtilities.isServerSide(code.getSystem())) {
1476          res = vsc.validateCode(path, code.copy());
1477          if (txCache != null && cachingAllowed) {
1478            txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
1479          }
1480          return res;
1481        }
1482      } catch (VSCheckerException e) {
1483        if (e.isWarning()) {
1484          localWarning = e.getMessage();
1485        } else {  
1486          localError = e.getMessage();
1487        }
1488        if (e.getIssues() != null) {
1489          issues.addAll(e.getIssues());
1490        }
1491        type = e.getType();
1492      } catch (TerminologyServiceProtectionException e) {
1493        OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, e.getType());
1494        iss.getDetails().setText(e.getMessage());
1495        iss.setDiagnostics(e.getDiagnostics());
1496        issues.add(iss);
1497        return new ValidationResult(IssueSeverity.FATAL, e.getMessage(), e.getError(), issues);
1498      } catch (Exception e) {
1499//        e.printStackTrace();!
1500        localError = e.getMessage();
1501      }
1502    }
1503    
1504    if (localError != null && !terminologyClientManager.hasClient()) {
1505      if (unknownSystems.size() > 0) {
1506        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues).setUnknownSystems(unknownSystems);
1507      } else if (type == TerminologyServiceErrorClass.INTERNAL_ERROR) {
1508        return new ValidationResult(IssueSeverity.FATAL, localError, TerminologyServiceErrorClass.INTERNAL_ERROR, issues);
1509      } else {
1510        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.UNKNOWN, issues);
1511      }
1512    }
1513    if (localWarning != null && !terminologyClientManager.hasClient()) {
1514      return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localWarning), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);       
1515    }
1516    if (!options.isUseServer()) {
1517      if (localWarning != null) {
1518        return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localWarning), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);       
1519      } else {
1520        return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localError), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);
1521      }
1522    }
1523    String codeKey = getCodeKey(code);
1524    if (unsupportedCodeSystems.contains(codeKey)) {
1525      return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.UNKNOWN_CODESYSTEM, code.getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues);      
1526    }
1527    
1528    // if that failed, we try to validate on the server
1529    if (noTerminologyServer) {
1530      return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, code.getCode(), code.getSystem()), TerminologyServiceErrorClass.NOSERVICE, issues);
1531    }
1532
1533    Set<String> systems = findRelevantSystems(code, vs);
1534    TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false);
1535    
1536    String csumm = cachingAllowed && txCache != null ? txCache.summary(code) : null;
1537    if (cachingAllowed && txCache != null) {
1538      txLog("$validate "+csumm+(vs == null ? "" : " for "+ txCache.summary(vs))+" on "+tc.getAddress());
1539    } else {
1540      txLog("$validate "+csumm+" before cache exists on "+tc.getAddress());
1541    }
1542    try {
1543      Parameters pIn = constructParameters(options, code);
1544      res = validateOnServer2(tc, vs, pIn, options, systems);
1545    } catch (Exception e) {
1546      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage(), null).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR);
1547    }
1548    if (!res.isOk() && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && (localError != null && !localError.equals(ValueSetValidator.NO_TRY_THE_SERVER))) {
1549      res = new ValidationResult(IssueSeverity.ERROR, localError, null).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(type);
1550    } 
1551    if (!res.isOk() && localError != null) {
1552      res.setDiagnostics("Local Error: "+localError.trim()+". Server Error: "+res.getMessage());
1553    } else if (!res.isOk() && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && res.getUnknownSystems() != null && res.getUnknownSystems().contains(codeKey) && localWarning != null) {
1554      // we had some problem evaluating locally, but the server doesn't know the code system, so we'll just go with the local error
1555      res = new ValidationResult(IssueSeverity.WARNING, localWarning, null);
1556      res.setDiagnostics("Local Warning: "+localWarning.trim()+". Server Error: "+res.getMessage());
1557      return res;
1558    }
1559    updateUnsupportedCodeSystems(res, code, codeKey);
1560    if (cachingAllowed && txCache != null) { // we never cache unsupported code systems - we always keep trying (but only once per run)
1561      txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
1562    }
1563    return res;
1564  }
1565
1566
1567  /**
1568   * ask the terminology system whether parent subsumes child. 
1569   * 
1570   * @return true if it does, false if it doesn't, and null if it's not know whether it does
1571   */
1572  public Boolean subsumes(ValidationOptions optionsArg, Coding parent, Coding child) {
1573    ValidationOptions options = optionsArg != null ? optionsArg : ValidationOptions.defaults();
1574
1575    if (parent.hasSystem()) {
1576      codeSystemsUsed.add(parent.getSystem());
1577    } else {
1578      return null;
1579    }
1580    if (child.hasSystem()) {
1581      codeSystemsUsed.add(child.getSystem());
1582    } else {
1583      return null;
1584    }
1585
1586    final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateSubsumesToken(options, parent, child, expParameters) : null;
1587    if (cachingAllowed && txCache != null) {
1588      Boolean res = txCache.getSubsumes(cacheToken);
1589      if (res != null) {
1590        return res;
1591      }
1592    }
1593    
1594    if (options.isUseClient() && parent.getSystem().equals(child.getSystem())) {
1595      CodeSystem cs = fetchCodeSystem(parent.getSystem());
1596      if (cs != null) {
1597        Boolean b = CodeSystemUtilities.subsumes(cs, parent.getCode(), child.getCode());
1598        if (txCache != null && cachingAllowed) {
1599          txCache.cacheSubsumes(cacheToken, b, true);
1600        }
1601        return b;
1602      }
1603    }
1604
1605    if (!terminologyClientManager.hasClient() || !options.isUseServer() || unsupportedCodeSystems.contains(parent.getSystem()) || unsupportedCodeSystems.contains(child.getSystem()) || noTerminologyServer) {
1606      return null;      
1607    }
1608
1609    Set<String> systems = new HashSet<>();
1610    systems.add(parent.getSystem());
1611    systems.add(child.getSystem());
1612    TerminologyClientContext tc = terminologyClientManager.chooseServer(null, systems, false);
1613    
1614    txLog("$subsumes "+parent.toString()+" > "+child.toString()+" on "+tc.getAddress());
1615
1616    try {
1617      Parameters pIn =  new Parameters();
1618      pIn.addParameter().setName("codingA").setValue(parent);
1619      pIn.addParameter().setName("codingB").setValue(child);
1620      if (txLog != null) {
1621        txLog.clearLastId();
1622      }
1623      Parameters pOut = tc.getClient().subsumes(pIn);
1624      return processSubsumesResult(pOut, tc.getClient().getAddress());
1625    } catch (Exception e) {
1626      // e.printStackTrace();
1627    }
1628    return null;
1629  }
1630
1631
1632  public Boolean processSubsumesResult(Parameters pOut, String server) {
1633    for (ParametersParameterComponent p : pOut.getParameter()) {
1634      if (p.hasValue()) {
1635        if (p.getName().equals("outcome")) {
1636          return Utilities.existsInList(p.getValue().primitiveValue(), "equivalent", "subsumes");
1637        }
1638      }
1639    }
1640    return null;
1641  }
1642
1643  protected ValueSetExpander constructValueSetExpanderSimple(ValidationOptions options) {
1644    return new ValueSetExpander(this, new TerminologyOperationContext(this, options, "expansion")).setDebug(logger.isDebugLogging());
1645  }
1646
1647  protected ValueSetValidator constructValueSetCheckerSimple(ValidationOptions options,  ValueSet vs,  ValidationContextCarrier ctxt) {
1648    return new ValueSetValidator(this, new TerminologyOperationContext(this, options, "validation"), options, vs, ctxt, expParameters, terminologyClientManager, registry);
1649  }
1650
1651  protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options,  ValueSet vs) {
1652    return new ValueSetValidator(this, new TerminologyOperationContext(this, options, "validation"), options, vs, expParameters, terminologyClientManager, registry);
1653  }
1654
1655  protected Parameters constructParameters(ITerminologyOperationDetails opCtxt, TerminologyClientContext tcd, ValueSet vs, boolean hierarchical) {
1656    Parameters p = expParameters.copy();
1657    p.setParameter("includeDefinition", false);
1658    p.setParameter("excludeNested", !hierarchical);
1659
1660    addDependentResources(opCtxt, tcd, p, vs);
1661    p.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
1662    return p;
1663  }
1664
1665  protected Parameters constructParameters(ValidationOptions options, Coding coding) {
1666    Parameters pIn = new Parameters();
1667    if (options.isGuessSystem()) {
1668      pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
1669      pIn.addParameter().setName("code").setValue(coding.getCodeElement());
1670    } else {
1671      pIn.addParameter().setName("coding").setValue(coding);
1672    }
1673    setTerminologyOptions(options, pIn);
1674    return pIn;
1675  }
1676
1677  protected Parameters constructParameters(ValidationOptions options, CodeableConcept codeableConcept) {
1678    Parameters pIn = new Parameters();
1679    pIn.addParameter().setName("codeableConcept").setValue(codeableConcept);
1680    setTerminologyOptions(options, pIn);
1681    return pIn;
1682  }
1683
1684  protected Parameters constructParameters(ValidationOptions options, CodingValidationRequest codingValidationRequest, ValueSet valueSet) {
1685    Parameters pIn = new Parameters();
1686    if (options.isGuessSystem()) {
1687      pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
1688      pIn.addParameter().setName("code").setValue(codingValidationRequest.getCoding().getCodeElement());
1689    } else {      
1690      pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
1691    }
1692    if (valueSet != null) {
1693      pIn.addParameter().setName("valueSet").setResource(valueSet);
1694    }
1695    
1696    pIn.addParameters(expParameters);
1697    return pIn;
1698  }
1699
1700  protected Parameters constructParameters(ValidationOptions options, CodingValidationRequest codingValidationRequest, String vsUrl) {
1701    Parameters pIn = new Parameters();
1702    if (options.isGuessSystem()) {
1703      pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
1704      pIn.addParameter().setName("code").setValue(codingValidationRequest.getCoding().getCodeElement());
1705    } else {
1706      pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
1707    }
1708    if (vsUrl != null) {
1709      pIn.addParameter().setName("url").setValue(new CanonicalType(vsUrl));
1710    }
1711    pIn.addParameters(expParameters);
1712    return pIn;
1713  }
1714
1715  private void updateUnsupportedCodeSystems(ValidationResult res, Coding code, String codeKey) {
1716    if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && !code.hasVersion() && fetchCodeSystem(codeKey) == null) {
1717      unsupportedCodeSystems.add(codeKey);
1718    }
1719  }
1720
1721  private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
1722    if (options.hasLanguages()) {
1723      pIn.addParameter("displayLanguage", options.getLanguages().toString());
1724    }
1725    if (options.isMembershipOnly()) {
1726      pIn.addParameter("valueset-membership-only", true);
1727    }
1728    if (options.isDisplayWarningMode()) {
1729      pIn.addParameter("lenient-display-validation", true);
1730    }
1731    if (options.isVersionFlexible()) {
1732      pIn.addParameter("default-to-latest-version", true);     
1733    }
1734  }
1735
1736  @Override
1737  public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) {
1738    CacheToken cacheToken = txCache.generateValidationToken(options, code, vs, expParameters);
1739    ValidationResult res = null;
1740    if (cachingAllowed) {
1741      res = txCache.getValidation(cacheToken);
1742      if (res != null) {
1743        return res;
1744      }
1745    }
1746    for (Coding c : code.getCoding()) {
1747      if (c.hasSystem()) {
1748        codeSystemsUsed.add(c.getSystem());
1749      }
1750    }
1751    Set<String> unknownSystems = new HashSet<>();
1752
1753    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1754    
1755    String localError = null;
1756    String localWarning = null;
1757    
1758    if (options.isUseClient()) {
1759      // ok, first we try to validate locally
1760      try {
1761        ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs);
1762        vsc.setUnknownSystems(unknownSystems);
1763        vsc.setThrowToServer(options.isUseServer() && terminologyClientManager.hasClient());
1764        res = vsc.validateCode("CodeableConcept", code);
1765        if (cachingAllowed) {
1766          txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
1767        }
1768        return res;
1769      } catch (VSCheckerException e) {
1770        if (e.isWarning()) {
1771          localWarning = e.getMessage();
1772        } else {  
1773          localError = e.getMessage();
1774        }
1775        if (e.getIssues() != null) {
1776          issues.addAll(e.getIssues());
1777        }
1778      } catch (TerminologyServiceProtectionException e) {
1779        OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, e.getType());
1780        iss.getDetails().setText(e.getMessage());
1781        iss.setDiagnostics(e.getDiagnostics());
1782        issues.add(iss);
1783        return new ValidationResult(IssueSeverity.FATAL, e.getMessage(), e.getError(), issues);
1784      } catch (Exception e) {
1785//        e.printStackTrace();
1786        localError = e.getMessage();
1787      }
1788    }
1789
1790    if (localError != null && !terminologyClientManager.hasClient()) {
1791      if (unknownSystems.size() > 0) {
1792        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues).setUnknownSystems(unknownSystems);
1793      } else {
1794        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.UNKNOWN, issues);
1795      }
1796    }
1797    if (localWarning != null && !terminologyClientManager.hasClient()) {
1798      return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localWarning), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);       
1799    }
1800    
1801    if (!options.isUseServer()) {
1802      return new ValidationResult(IssueSeverity.WARNING, "Unable to validate code without using server", TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null);      
1803    }
1804    
1805    // if that failed, we try to validate on the server
1806    if (noTerminologyServer) {
1807      return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE, null);
1808    }
1809    Set<String> systems = findRelevantSystems(code, vs);
1810    TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false);
1811
1812    txLog("$validate "+txCache.summary(code)+" for "+ txCache.summary(vs)+" on "+tc.getAddress());
1813    try {
1814      Parameters pIn = constructParameters(options, code);
1815      res = validateOnServer2(tc, vs, pIn, options, systems);
1816    } catch (Exception e) {
1817      issues.clear();
1818      OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXCEPTION);
1819      iss.getDetails().setText(e.getMessage());
1820      issues.add(iss);
1821      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage(), issues).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR);
1822    }
1823    if (cachingAllowed) {
1824      txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
1825    }
1826    return res;
1827  }
1828
1829  private Set<String> findRelevantSystems(ValueSet vs) {
1830    Set<String> set = new HashSet<>();
1831    if (vs != null) {
1832      findRelevantSystems(set, vs);
1833    }
1834    return set;
1835  }
1836
1837  private Set<String> findRelevantSystems(CodeableConcept code, ValueSet vs) {
1838    Set<String> set = new HashSet<>();
1839    if (vs != null) {
1840      findRelevantSystems(set, vs);
1841    }
1842    for (Coding c : code.getCoding()) {      
1843      findRelevantSystems(set, c);
1844    }
1845    return set;
1846  }
1847
1848  private Set<String> findRelevantSystems(Coding code, ValueSet vs) {
1849    Set<String> set = new HashSet<>();
1850    if (vs != null) {
1851      findRelevantSystems(set, vs);
1852    }
1853    if (code != null) {      
1854      findRelevantSystems(set, code);
1855    }
1856    return set;
1857  }
1858
1859  private void findRelevantSystems(Set<String> set, ValueSet vs) {
1860    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1861      findRelevantSystems(set, inc);
1862    }
1863    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1864      findRelevantSystems(set, inc);
1865    }    
1866  }
1867
1868  private void findRelevantSystems(Set<String> set, ConceptSetComponent inc) {
1869    if (inc.hasSystem()) {
1870      if (inc.hasVersion()) {
1871        set.add(inc.getSystem()+"|"+inc.getVersion());
1872      } else {
1873        set.add(inc.getSystem());
1874      }
1875    }
1876    for (CanonicalType u : inc.getValueSet()) {
1877      ValueSet vs = fetchResource(ValueSet.class, u.getValue());
1878      if (vs != null) {
1879        findRelevantSystems(set, vs);
1880      } else {
1881        set.add(TerminologyClientManager.UNRESOLVED_VALUESET);
1882      }
1883    }
1884  }
1885
1886  private void findRelevantSystems(Set<String> set, Coding c) {
1887    if (c.hasSystem()) {
1888      if (c.hasVersion()) {
1889        set.add(c.getSystem()+"|"+c.getVersion());
1890      } else {
1891        set.add(c.getSystem());
1892      }
1893    }    
1894  }
1895
1896  protected ValidationResult validateOnServer(TerminologyClientContext tc, ValueSet vs, Parameters pin, ValidationOptions options) throws FHIRException {
1897    return  validateOnServer2(tc, vs, pin, options, null);
1898  }
1899  
1900  protected ValidationResult validateOnServer2(TerminologyClientContext tc, ValueSet vs, Parameters pin, ValidationOptions options, Set<String> systems) throws FHIRException {
1901
1902    if (vs != null) {
1903      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1904        codeSystemsUsed.add(inc.getSystem());
1905      }
1906      for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1907        codeSystemsUsed.add(inc.getSystem());
1908      }
1909    }
1910
1911    addServerValidationParameters(null, tc, vs, pin, options, systems);
1912
1913    if (txLog != null) {
1914      txLog.clearLastId();
1915    }
1916    if (tc == null) {
1917      throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1918    }
1919    Parameters pOut;
1920    if (vs == null) {
1921      pOut = tc.getClient().validateCS(pin);
1922    } else {
1923      pOut = tc.getClient().validateVS(pin);
1924    }
1925    return processValidationResult(pOut, vs == null ? null : vs.getUrl(), tc.getClient().getAddress());
1926  }
1927
1928  protected void addServerValidationParameters(ITerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options) {
1929    addServerValidationParameters(opCtxt, terminologyClientContext, vs, pin, options, null);
1930  }
1931  
1932  protected void addServerValidationParameters(ITerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options, Set<String> systems) {
1933    boolean cache = false;
1934    if (vs != null) {
1935      if (terminologyClientContext != null && terminologyClientContext.isTxCaching() && terminologyClientContext.getCacheId() != null && vs.getUrl() != null && terminologyClientContext.getCached().contains(vs.getUrl()+"|"+ vs.getVersion())) {
1936        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
1937        if (vs.hasVersion()) {
1938          pin.addParameter().setName("valueSetVersion").setValue(new StringType(vs.getVersion()));            
1939        }
1940      } else if (options.getVsAsUrl()){
1941        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
1942      } else {
1943        if (vs.hasCompose() && vs.hasExpansion()) {
1944          vs = vs.copy();
1945          vs.setExpansion(null);
1946        }
1947        pin.addParameter().setName("valueSet").setResource(vs);
1948        if (vs.getUrl() != null) {
1949          terminologyClientContext.getCached().add(vs.getUrl()+"|"+ vs.getVersion());
1950        }
1951      }
1952      cache = true;
1953      addDependentResources(opCtxt, terminologyClientContext, pin, vs);
1954    }
1955    if (systems != null) {
1956      for (String s : systems) {
1957        cache = addDependentCodeSystem(opCtxt, terminologyClientContext, pin, s, null) || cache;
1958      }
1959    }
1960    pin.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
1961    for (ParametersParameterComponent pp : pin.getParameter()) {
1962      if (pp.getName().equals("profile")) {
1963        throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT));
1964      }
1965    }
1966    if (expParameters == null) {
1967      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1968    }
1969    for (ParametersParameterComponent pp : expParameters.getParameter()) {
1970      if (!pin.hasParameter(pp.getName())) {
1971        pin.addParameter(pp);
1972      }
1973    }
1974
1975    if (options.isDisplayWarningMode()) {
1976      pin.addParameter("mode","lenient-display-validation");
1977    }
1978    pin.addParameter("diagnostics", true);
1979  }
1980
1981  private boolean addDependentResources(ITerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ValueSet vs) {
1982    boolean cache = false;
1983    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1984      cache = addDependentResources(opCtxt, tc, pin, inc, vs) || cache;
1985    }
1986    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1987      cache = addDependentResources(opCtxt, tc, pin, inc, vs) || cache;
1988    }
1989    return cache;
1990  }
1991
1992  private boolean addDependentResources(ITerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ConceptSetComponent inc, Resource src) {
1993    boolean cache = false;
1994    for (CanonicalType c : inc.getValueSet()) {
1995      ValueSet vs = fetchResource(ValueSet.class, c.getValue(), src);
1996      if (vs != null && !hasCanonicalResource(pin, "tx-resource", vs.getVUrl())) {
1997        cache = checkAddToParams(tc, pin, vs) || cache;
1998        addDependentResources(opCtxt, tc, pin, vs);
1999        for (Extension ext : vs.getExtensionsByUrl(ToolingExtensions.EXT_VS_CS_SUPPL_NEEDED)) {
2000          if (ext.hasValueCanonicalType()) {
2001            String url = ext.getValueCanonicalType().asStringValue();
2002            CodeSystem supp = fetchResource(CodeSystem.class, url);
2003            if (supp != null) {
2004              if (opCtxt != null) {
2005                opCtxt.seeSupplement(supp);
2006              }
2007              cache = checkAddToParams(tc, pin, supp) || cache;            
2008            }
2009          }
2010        }
2011      }
2012    }
2013    String sys = inc.getSystem();
2014    cache = addDependentCodeSystem(opCtxt, tc, pin, sys, src) || cache;
2015    return cache;
2016  }
2017
2018  public boolean addDependentCodeSystem(ITerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, String sys, Resource src) {
2019    boolean cache = false;
2020    CodeSystem cs = fetchResource(CodeSystem.class, sys, src);
2021    if (cs != null && !hasCanonicalResource(pin, "tx-resource", cs.getVUrl()) && (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
2022      cache = checkAddToParams(tc, pin, cs) || cache;
2023    }
2024    for (CodeSystem supp : codeSystems.getSupplements(cs)) {
2025      if (opCtxt != null) {
2026        opCtxt.seeSupplement(supp);
2027      }
2028      if (!hasCanonicalResource(pin, "tx-resource", supp.getVUrl()) ) {
2029        cache = checkAddToParams(tc, pin, supp) || cache;
2030      }
2031    }
2032    if (sys != null) {
2033      // we also have to look at this by version because the resource might not be versioned or we might not have a copy
2034      for (CodeSystem supp : codeSystems.getSupplements(sys)) {
2035
2036        if (opCtxt != null) {
2037          opCtxt.seeSupplement(supp);
2038        }
2039        if (!hasCanonicalResource(pin, "tx-resource", supp.getVUrl()) ) {
2040          cache = checkAddToParams(tc, pin, supp) || cache;
2041        }
2042      }
2043      if (!sys.contains("!")) {
2044        sys = getFixedVersion(sys, pin);
2045        if (sys != null) {
2046          for (CodeSystem supp : codeSystems.getSupplements(sys)) {
2047            if (opCtxt != null) {
2048              opCtxt.seeSupplement(supp);
2049            }
2050            if (!hasCanonicalResource(pin, "tx-resource", supp.getVUrl()) ) {
2051              cache = checkAddToParams(tc, pin, supp) || cache;
2052            }
2053          }
2054        }
2055      }
2056    }
2057    return cache;
2058  }
2059
2060  private String getFixedVersion(String sys, Parameters pin) {
2061    for (ParametersParameterComponent p : pin.getParameter()) {
2062      if (Utilities.existsInList(p.getName(), "system-version", "force-system-version", "default-system-version")) {
2063        if (p.hasValuePrimitive() && p.getValue().primitiveValue() != null && p.getValue().primitiveValue().startsWith(sys)) {
2064          return p.getValue().primitiveValue();
2065        }
2066      }
2067    }
2068    return null;
2069  }
2070
2071  private boolean checkAddToParams(TerminologyClientContext tc, Parameters pin, CanonicalResource cr) {
2072    boolean cache = false;
2073    boolean addToParams = false;
2074    if (tc.usingCache()) {
2075      if (!tc.alreadyCached(cr)) {
2076        tc.addToCache(cr);
2077        if (logger.isDebugLogging()) {
2078          logger.logMessage("add to cache: "+cr.getVUrl());
2079        }
2080        addToParams = true;
2081        cache = true;
2082      } else {
2083        if (logger.isDebugLogging()) {
2084          logger.logMessage("already cached: "+cr.getVUrl());
2085        }
2086      }
2087    } else {
2088      addToParams = true;
2089    }
2090    if (addToParams) {
2091      pin.addParameter().setName("tx-resource").setResource(cr);
2092    }
2093    return cache;
2094  }
2095
2096  private boolean hasCanonicalResource(Parameters pin, String name, String vUrl) {
2097    for (ParametersParameterComponent p : pin.getParameter()) {
2098      if (name.equals(p.getName()) && p.hasResource() &&
2099          p.getResource() instanceof CanonicalResource && vUrl.equals(((CanonicalResource) p.getResource()).getVUrl())) {
2100        return true;
2101      }
2102    }
2103    return false;
2104  }
2105
2106  public ValidationResult processValidationResult(Parameters pOut, String vs, String server) {
2107    boolean ok = false;
2108    String message = "No Message returned";
2109    String display = null;
2110    String system = null;
2111    String code = null;
2112    String version = null;
2113    boolean inactive = false;
2114    String status = null;
2115    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
2116    Set<String> unknownSystems = new HashSet<>();
2117
2118    TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN;
2119    for (ParametersParameterComponent p : pOut.getParameter()) {
2120      if (p.hasValue()) {
2121        if (p.getName().equals("result")) {
2122          ok = ((BooleanType) p.getValue()).getValue().booleanValue();
2123        } else if (p.getName().equals("message")) {
2124          message = p.getValue().primitiveValue();
2125        } else if (p.getName().equals("display")) {
2126          display = p.getValue().primitiveValue();
2127        } else if (p.getName().equals("system")) {
2128          system = ((PrimitiveType<?>) p.getValue()).asStringValue();
2129        } else if (p.getName().equals("version")) {
2130          version = ((PrimitiveType<?>) p.getValue()).asStringValue();
2131        } else if (p.getName().equals("code")) {
2132          code = ((PrimitiveType<?>) p.getValue()).asStringValue();
2133        } else if (p.getName().equals("inactive")) {
2134          inactive = "true".equals(((PrimitiveType<?>) p.getValue()).asStringValue());
2135        } else if (p.getName().equals("status")) {
2136          status = ((PrimitiveType<?>) p.getValue()).asStringValue();
2137        } else if (p.getName().equals("x-caused-by-unknown-system")) {
2138          String unkSystem = ((PrimitiveType<?>) p.getValue()).asStringValue();
2139          if (unkSystem != null && unkSystem.contains("|")) {
2140            err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED_VERSION; 
2141            system = unkSystem.substring(0, unkSystem.indexOf("|"));
2142            version = unkSystem.substring(unkSystem.indexOf("|")+1);
2143          } else {
2144            err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;            
2145            unknownSystems.add(unkSystem);      
2146          }
2147        } else if (p.getName().equals("x-unknown-system")) {
2148          unknownSystems.add(((PrimitiveType<?>) p.getValue()).asStringValue());      
2149        } else if (p.getName().equals("warning-withdrawn")) {
2150          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2151          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2152          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_WITHDRAWN : I18nConstants.MSG_WITHDRAWN_SRC, msg, vs, impliedType(msg)));              
2153          issues.add(iss);
2154        } else if (p.getName().equals("warning-deprecated")) {
2155          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2156          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2157          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DEPRECATED : I18nConstants.MSG_DEPRECATED_SRC, msg, vs, impliedType(msg)));              
2158          issues.add(iss);
2159        } else if (p.getName().equals("warning-retired")) {
2160          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2161          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2162          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_RETIRED : I18nConstants.MSG_RETIRED_SRC, msg, vs, impliedType(msg)));              
2163          issues.add(iss);
2164        } else if (p.getName().equals("warning-experimental")) {
2165          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2166          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2167          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_EXPERIMENTAL : I18nConstants.MSG_EXPERIMENTAL_SRC, msg, vs, impliedType(msg)));              
2168          issues.add(iss);
2169        } else if (p.getName().equals("warning-draft")) {
2170          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2171          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2172          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DRAFT : I18nConstants.MSG_DRAFT_SRC, msg, vs, impliedType(msg)));              
2173          issues.add(iss);
2174        } else if (p.getName().equals("cause")) {
2175          try {
2176            IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue());
2177            if (it == IssueType.UNKNOWN) {
2178              err = TerminologyServiceErrorClass.UNKNOWN;
2179            } else if (it == IssueType.NOTFOUND) {
2180              err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
2181            } else if (it == IssueType.NOTSUPPORTED) {
2182              err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED;
2183            } else {
2184              err = null;
2185            }
2186          } catch (FHIRException e) {
2187          }
2188        }
2189      } else if (p.hasResource()) {
2190        if (p.getName().equals("issues")) {
2191          OperationOutcome oo = (OperationOutcome) p.getResource();
2192          for (OperationOutcomeIssueComponent iss : oo.getIssue()) {
2193            iss.addExtension(ToolingExtensions.EXT_ISSUE_SERVER, new UrlType(server));
2194            issues.add(iss);
2195          }
2196        } else {
2197          // nothing?
2198        }
2199      }
2200    }
2201    ValidationResult res = null;
2202    if (!ok) {
2203      res = new ValidationResult(IssueSeverity.ERROR, message, err, null).setTxLink(txLog.getLastId());
2204      if (code != null) {
2205        res.setDefinition(new ConceptDefinitionComponent().setDisplay(display).setCode(code));
2206        res.setDisplay(display);
2207      }
2208      if (system != null) {
2209        res.setSystem(system);
2210      }
2211      if (version != null) {
2212        res.setVersion(version);
2213      }
2214    } else if (message != null && !message.equals("No Message returned")) { 
2215      res = new ValidationResult(IssueSeverity.WARNING, message, system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog.getLastId());
2216    } else if (display != null) {
2217      res = new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog.getLastId());
2218    } else {
2219      res = new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog.getLastId());
2220    }
2221    res.setIssues(issues);
2222    res.setStatus(inactive, status);
2223    res.setUnknownSystems(unknownSystems);
2224    res.setServer(server);
2225    return res;
2226  }
2227
2228  // --------------------------------------------------------------------------------------------------------------------------------------------------------
2229  
2230  private Object impliedType(String msg) {
2231    if (msg.contains("/CodeSystem")) {
2232      return "CodeSystem";
2233    }
2234    if (msg.contains("/ValueSet")) {
2235      return "ValueSet";
2236    }
2237    return "item";
2238  }
2239
2240  public void initTxCache(String cachePath) throws FileNotFoundException, FHIRException, IOException {
2241    if (cachePath != null) {
2242      txCache = new TerminologyCache(lock, cachePath);
2243      initTxCache(txCache);
2244    }
2245  }
2246  
2247  public void initTxCache(TerminologyCache cache) {
2248    txCache = cache;
2249    terminologyClientManager.setCache(txCache);
2250  }
2251
2252  public void clearTSCache(String url) throws Exception {
2253    txCache.removeCS(url);
2254  }
2255
2256  public boolean isCanRunWithoutTerminology() {
2257    return canRunWithoutTerminology;
2258  }
2259
2260  public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) {
2261    this.canRunWithoutTerminology = canRunWithoutTerminology;
2262  }
2263
2264  public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger) {
2265    this.logger = logger;
2266    getTxClientManager().setLogger(logger);
2267  }
2268
2269  public Parameters getExpansionParameters() {
2270    return expParameters;
2271  }
2272
2273  public void setExpansionParameters(Parameters expParameters) {
2274    this.expParameters = expParameters;
2275    this.terminologyClientManager.setExpansionParameters(expParameters);
2276  }
2277
2278  @Override
2279  public boolean isNoTerminologyServer() {
2280    return noTerminologyServer || !terminologyClientManager.hasClient();
2281  }
2282
2283  public void setNoTerminologyServer(boolean noTerminologyServer) {
2284    this.noTerminologyServer = noTerminologyServer;
2285  }
2286
2287  public String getName() {
2288    return name;
2289  }
2290
2291  public void setName(String name) {
2292    this.name = name;
2293  }
2294
2295
2296  public List<String> getResourceNames(FhirPublication fhirVersion) {
2297    return getResourceNames();    
2298  }
2299  
2300  public Set<String> getResourceNamesAsSet(FhirPublication fhirVersion) {
2301    return getResourceNamesAsSet();
2302  }
2303  
2304  @Override
2305  public Set<String> getResourceNamesAsSet() {
2306    Set<String> res = new HashSet<String>();
2307    res.addAll(getResourceNames());
2308    return res;
2309  }
2310
2311  public boolean isAllowLoadingDuplicates() {
2312    return allowLoadingDuplicates;
2313  }
2314
2315  public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) {
2316    this.allowLoadingDuplicates = allowLoadingDuplicates;
2317  }
2318
2319  @Override
2320  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
2321    return fetchResourceWithException(class_, uri, null);
2322  }
2323  
2324  public <T extends Resource> T fetchResourceWithException(String cls, String uri) throws FHIRException {
2325    return fetchResourceWithExceptionByVersion(cls, uri, null, null);
2326  }
2327  
2328  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, Resource sourceForReference) throws FHIRException {
2329    return fetchResourceWithExceptionByVersion(class_, uri, null, sourceForReference);
2330  }
2331  
2332  @SuppressWarnings("unchecked")
2333  public <T extends Resource> T fetchResourceWithExceptionByVersion(Class<T> class_, String uri, String version, Resource sourceForReference) throws FHIRException {
2334    if (uri == null) {
2335      return null;
2336    }
2337    if (uri.startsWith("#")) {
2338      if (sourceForReference != null && sourceForReference instanceof DomainResource) {
2339        for (Resource r : ((DomainResource) sourceForReference).getContained()) {
2340          if (r.getClass() == class_ &&( "#"+r.getIdBase()).equals(uri)) {
2341            if (r instanceof CanonicalResource) {
2342              CanonicalResource cr = (CanonicalResource) r;
2343              if (!cr.hasUrl()) {
2344                cr.setUrl(UUIDUtilities.makeUuidUrn());
2345              }              
2346            }
2347            return (T) r;
2348          }
2349        }
2350      }
2351      return null;
2352    }
2353    
2354    if (QA_CHECK_REFERENCE_SOURCE) {
2355      // it can be tricky to trace the source of a reference correctly. The code isn't water tight,
2356      // particularly around snapshot generation. Enable this code to check that the references are 
2357      // correct (but it's slow)
2358      if (sourceForReference != null && uri.contains("ValueSet")) {
2359        if (!ResourceUtilities.hasURL(uri, sourceForReference)) {
2360          System.out.print("Claimed source doesn't have url in it: "+sourceForReference.fhirType()+"/"+sourceForReference.getIdPart()+" -> "+uri);
2361          System.out.println();
2362        }
2363      }
2364    }
2365   
2366    List<String> pvlist = new ArrayList<>();
2367    if (sourceForReference != null && sourceForReference.getSourcePackage() != null) {
2368      populatePVList(pvlist, sourceForReference.getSourcePackage());
2369    }
2370    
2371    if (class_ == StructureDefinition.class) {
2372      uri = ProfileUtilities.sdNs(uri, null);
2373    }
2374    synchronized (lock) {
2375
2376      if (version == null) {
2377        if (uri.contains("|")) {
2378          version = uri.substring(uri.lastIndexOf("|")+1);
2379          uri = uri.substring(0, uri.lastIndexOf("|"));
2380        }
2381      } else {
2382        assert !uri.contains("|");
2383      }
2384      if (uri.contains("#")) {
2385        uri = uri.substring(0, uri.indexOf("#"));
2386      } 
2387      if (class_ == Resource.class || class_ == null) {
2388        if (structures.has(uri)) {
2389          return (T) structures.get(uri, version, pvlist);
2390        }        
2391        if (guides.has(uri)) {
2392          return (T) guides.get(uri, version, pvlist);
2393        } 
2394        if (capstmts.has(uri)) {
2395          return (T) capstmts.get(uri, version, pvlist);
2396        } 
2397        if (measures.has(uri)) {
2398          return (T) measures.get(uri, version, pvlist);
2399        } 
2400        if (libraries.has(uri)) {
2401          return (T) libraries.get(uri, version, pvlist);
2402        } 
2403        if (valueSets.has(uri)) {
2404          return (T) valueSets.get(uri, version, pvlist);
2405        } 
2406        if (codeSystems.has(uri)) {
2407          return (T) codeSystems.get(uri, version, pvlist);
2408        } 
2409        if (systems.has(uri)) {
2410          return (T) systems.get(uri, version, pvlist);
2411        } 
2412        if (operations.has(uri)) {
2413          return (T) operations.get(uri, version, pvlist);
2414        } 
2415        if (searchParameters.has(uri)) {
2416          return (T) searchParameters.get(uri, version, pvlist);
2417        } 
2418        if (plans.has(uri)) {
2419          return (T) plans.get(uri, version, pvlist);
2420        } 
2421        if (maps.has(uri)) {
2422          return (T) maps.get(uri, version, pvlist);
2423        } 
2424        if (transforms.has(uri)) {
2425          return (T) transforms.get(uri, version, pvlist);
2426        } 
2427        if (actors.has(uri)) {
2428          return (T) actors.get(uri, version, pvlist);
2429        } 
2430        if (requirements.has(uri)) {
2431          return (T) requirements.get(uri, version, pvlist);
2432        } 
2433        if (questionnaires.has(uri)) {
2434          return (T) questionnaires.get(uri, version, pvlist);
2435        } 
2436
2437        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
2438          for (ResourceProxy r : rt.values()) {
2439            if (uri.equals(r.getUrl())) {
2440              if (version == null || version == r.getResource().getMeta().getVersionId()) {
2441                return (T) r.getResource();
2442              }
2443            }
2444          }            
2445        }
2446        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
2447          return null;
2448        }
2449
2450        // it might be a special URL.
2451//        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
2452//          Resource res = null; // findTxValueSet(uri);
2453//          if (res != null) {
2454//            return (T) res;
2455//          }
2456//        }
2457        return null;      
2458      } else if (class_ == ImplementationGuide.class) {
2459        return (T) guides.get(uri, version, pvlist);
2460      } else if (class_ == CapabilityStatement.class) {
2461        return (T) capstmts.get(uri, version, pvlist);
2462      } else if (class_ == Measure.class) {
2463        return (T) measures.get(uri, version, pvlist);
2464      } else if (class_ == Library.class) {
2465        return (T) libraries.get(uri, version, pvlist);
2466      } else if (class_ == StructureDefinition.class) {
2467        return (T) structures.get(uri, version, pvlist);
2468      } else if (class_ == StructureMap.class) {
2469        return (T) transforms.get(uri, version, pvlist);
2470      } else if (class_ == NamingSystem.class) {
2471        return (T) systems.get(uri, version, pvlist);
2472      } else if (class_ == ValueSet.class) {
2473        return (T) valueSets.get(uri, version, pvlist);
2474      } else if (class_ == CodeSystem.class) {
2475        return (T) codeSystems.get(uri, version, pvlist);
2476      } else if (class_ == ConceptMap.class) {
2477        return (T) maps.get(uri, version, pvlist);
2478      } else if (class_ == ActorDefinition.class) {
2479        return (T) actors.get(uri, version, pvlist);
2480      } else if (class_ == Requirements.class) {
2481        return (T) requirements.get(uri, version, pvlist);
2482      } else if (class_ == PlanDefinition.class) {
2483        return (T) plans.get(uri, version, pvlist);
2484      } else if (class_ == OperationDefinition.class) {
2485        OperationDefinition od = operations.get(uri, version);
2486        return (T) od;
2487      } else if (class_ == Questionnaire.class) {
2488        return (T) questionnaires.get(uri, version, pvlist);
2489      } else if (class_ == SearchParameter.class) {
2490        SearchParameter res = searchParameters.get(uri, version, pvlist);
2491        return (T) res;
2492      }
2493      if (class_ == CodeSystem.class && codeSystems.has(uri)) { 
2494        return (T) codeSystems.get(uri, version, pvlist);
2495      }
2496      if (class_ == ValueSet.class && valueSets.has(uri)) {
2497        return (T) valueSets.get(uri, version, pvlist);
2498      } 
2499      
2500      if (class_ == Questionnaire.class) {
2501        return (T) questionnaires.get(uri, version, pvlist);
2502      } 
2503      if (supportedCodeSystems.contains(uri)) {
2504        return null;
2505      } 
2506      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
2507    }
2508  }
2509
2510  private void populatePVList(List<String> pvlist, PackageInformation sourcePackage) {
2511    pvlist.add(sourcePackage.getVID());
2512    List<String> toadd = new ArrayList<>();
2513    do {
2514      toadd.clear();
2515      for (String s : pvlist) {
2516        PackageInformation pi = packages.get(s);
2517        if (pi != null) {
2518          for (String v : pi.getDependencies()) {
2519            if (!pvlist.contains(v) && !toadd.contains(v)) {
2520              toadd.add(v);
2521            }
2522          }
2523        }        
2524      }
2525      pvlist.addAll(toadd);
2526    } while (toadd.size() > 0);
2527  }
2528
2529  public PackageInformation getPackageForUrl(String uri) {
2530    if (uri == null) {
2531      return null;
2532    }
2533    uri = ProfileUtilities.sdNs(uri, null);
2534
2535    synchronized (lock) {
2536
2537      String version = null;
2538      if (uri.contains("|")) {
2539        version = uri.substring(uri.lastIndexOf("|")+1);
2540        uri = uri.substring(0, uri.lastIndexOf("|"));
2541      }
2542      if (uri.contains("#")) {
2543        uri = uri.substring(0, uri.indexOf("#"));
2544      } 
2545      if (structures.has(uri)) {
2546        return structures.getPackageInfo(uri, version);
2547      }        
2548      if (guides.has(uri)) {
2549        return guides.getPackageInfo(uri, version);
2550      } 
2551      if (capstmts.has(uri)) {
2552        return capstmts.getPackageInfo(uri, version);
2553      } 
2554      if (measures.has(uri)) {
2555        return measures.getPackageInfo(uri, version);
2556      } 
2557      if (libraries.has(uri)) {
2558        return libraries.getPackageInfo(uri, version);
2559      } 
2560      if (valueSets.has(uri)) {
2561        return valueSets.getPackageInfo(uri, version);
2562      } 
2563      if (codeSystems.has(uri)) {
2564        return codeSystems.getPackageInfo(uri, version);
2565      } 
2566      if (operations.has(uri)) {
2567        return operations.getPackageInfo(uri, version);
2568      } 
2569      if (searchParameters.has(uri)) {
2570        return searchParameters.getPackageInfo(uri, version);
2571      } 
2572      if (plans.has(uri)) {
2573        return plans.getPackageInfo(uri, version);
2574      } 
2575      if (maps.has(uri)) {
2576        return maps.getPackageInfo(uri, version);
2577      } 
2578      if (transforms.has(uri)) {
2579        return transforms.getPackageInfo(uri, version);
2580      } 
2581      if (actors.has(uri)) {
2582        return actors.getPackageInfo(uri, version);
2583      } 
2584      if (requirements.has(uri)) {
2585        return requirements.getPackageInfo(uri, version);
2586      } 
2587      if (questionnaires.has(uri)) {
2588        return questionnaires.getPackageInfo(uri, version);
2589      }         
2590      return null;
2591    }
2592  }
2593  
2594  @SuppressWarnings("unchecked")
2595  public <T extends Resource> T fetchResourceWithExceptionByVersion(String cls, String uri, String version, CanonicalResource source) throws FHIRException {
2596    if (uri == null) {
2597      return null;
2598    }
2599   
2600    if ("StructureDefinition".equals(cls)) {
2601      uri = ProfileUtilities.sdNs(uri, null);
2602    }
2603    synchronized (lock) {
2604
2605      if (version == null) {
2606        if (uri.contains("|")) {
2607          version = uri.substring(uri.lastIndexOf("|")+1);
2608          uri = uri.substring(0, uri.lastIndexOf("|"));
2609        }
2610      } else {
2611        boolean b = !uri.contains("|");
2612        assert b;
2613      }
2614      if (uri.contains("#")) {
2615        uri = uri.substring(0, uri.indexOf("#"));
2616      } 
2617      if (cls == null || "Resource".equals(cls)) {
2618        if (structures.has(uri)) {
2619          return (T) structures.get(uri, version);
2620        } 
2621        if (guides.has(uri)) {
2622          return (T) guides.get(uri, version);
2623        } 
2624        if (capstmts.has(uri)) {
2625          return (T) capstmts.get(uri, version);
2626        } 
2627        if (measures.has(uri)) {
2628          return (T) measures.get(uri, version);
2629        } 
2630        if (libraries.has(uri)) {
2631          return (T) libraries.get(uri, version);
2632        } 
2633        if (valueSets.has(uri)) {
2634          return (T) valueSets.get(uri, version);
2635        } 
2636        if (codeSystems.has(uri)) {
2637          return (T) codeSystems.get(uri, version);
2638        } 
2639        if (operations.has(uri)) {
2640          return (T) operations.get(uri, version);
2641        } 
2642        if (searchParameters.has(uri)) {
2643          return (T) searchParameters.get(uri, version);
2644        } 
2645        if (plans.has(uri)) {
2646          return (T) plans.get(uri, version);
2647        } 
2648        if (maps.has(uri)) {
2649          return (T) maps.get(uri, version);
2650        } 
2651        if (transforms.has(uri)) {
2652          return (T) transforms.get(uri, version);
2653        } 
2654        if (actors.has(uri)) {
2655          return (T) actors.get(uri, version);
2656        } 
2657        if (requirements.has(uri)) {
2658          return (T) requirements.get(uri, version);
2659        } 
2660        if (questionnaires.has(uri)) {
2661          return (T) questionnaires.get(uri, version);
2662        } 
2663        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
2664          for (ResourceProxy r : rt.values()) {
2665            if (uri.equals(r.getUrl())) {
2666              return (T) r.getResource();
2667            }
2668          }            
2669        }
2670      } else if ("ImplementationGuide".equals(cls)) {
2671        return (T) guides.get(uri, version);
2672      } else if ("CapabilityStatement".equals(cls)) {
2673        return (T) capstmts.get(uri, version);
2674      } else if ("Measure".equals(cls)) {
2675        return (T) measures.get(uri, version);
2676      } else if ("Library".equals(cls)) {
2677        return (T) libraries.get(uri, version);
2678      } else if ("StructureDefinition".equals(cls)) {
2679        return (T) structures.get(uri, version);
2680      } else if ("StructureMap".equals(cls)) {
2681        return (T) transforms.get(uri, version);
2682      } else if ("Requirements".equals(cls)) {
2683        return (T) requirements.get(uri, version);
2684      } else if ("ActorDefinition".equals(cls)) {
2685        return (T) actors.get(uri, version);
2686      } else if ("ValueSet".equals(cls)) {
2687        return (T) valueSets.get(uri, version);
2688      } else if ("CodeSystem".equals(cls)) {
2689        return (T) codeSystems.get(uri, version);
2690      } else if ("NamingSystem".equals(cls)) {
2691        return (T) systems.get(uri, version);
2692      } else if ("ConceptMap".equals(cls)) {
2693        return (T) maps.get(uri, version);
2694      } else if ("PlanDefinition".equals(cls)) {
2695        return (T) plans.get(uri, version);
2696      } else if ("OperationDefinition".equals(cls)) {
2697        OperationDefinition od = operations.get(uri, version);
2698        return (T) od;
2699      } else if ("Questionnaire".equals(cls)) {
2700        return (T) questionnaires.get(uri, version);
2701      } else if ("SearchParameter".equals(cls)) {
2702        SearchParameter res = searchParameters.get(uri, version);
2703        return (T) res;
2704      }
2705      if ("CodeSystem".equals(cls) && codeSystems.has(uri)) {
2706        return (T) codeSystems.get(uri, version);
2707      } 
2708      if ("ValueSet".equals(cls) && valueSets.has(uri)) {
2709        return (T) valueSets.get(uri, version);
2710      } 
2711      
2712      if ("Questionnaire".equals(cls)) {
2713        return (T) questionnaires.get(uri, version);
2714      } 
2715      if (cls == null) {
2716        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
2717          return null;
2718        } 
2719
2720        // it might be a special URL.
2721        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
2722          Resource res = null; // findTxValueSet(uri);
2723          if (res != null) {
2724            return (T) res;
2725          } 
2726        }
2727        return null;      
2728      }    
2729      if (supportedCodeSystems.contains(uri)) {
2730        return null;
2731      } 
2732      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
2733    }
2734  }
2735  
2736  public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_, FhirPublication fhirVersion) {
2737    return fetchResourcesByType(class_);
2738  }
2739  
2740  @SuppressWarnings("unchecked")
2741  public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_) {
2742
2743    List<T> res = new ArrayList<>();
2744
2745    synchronized (lock) {
2746
2747      if (class_ == Resource.class || class_ == DomainResource.class || class_ == CanonicalResource.class || class_ == null) {
2748        res.addAll((List<T>) structures.getList());
2749        res.addAll((List<T>) guides.getList());
2750        res.addAll((List<T>) capstmts.getList());
2751        res.addAll((List<T>) measures.getList());
2752        res.addAll((List<T>) libraries.getList());
2753        res.addAll((List<T>) valueSets.getList());
2754        res.addAll((List<T>) codeSystems.getList());
2755        res.addAll((List<T>) operations.getList());
2756        res.addAll((List<T>) searchParameters.getList());
2757        res.addAll((List<T>) plans.getList());
2758        res.addAll((List<T>) maps.getList());
2759        res.addAll((List<T>) transforms.getList());
2760        res.addAll((List<T>) questionnaires.getList());
2761        res.addAll((List<T>) systems.getList());
2762        res.addAll((List<T>) actors.getList());
2763        res.addAll((List<T>) requirements.getList());
2764      } else if (class_ == ImplementationGuide.class) {
2765        res.addAll((List<T>) guides.getList());
2766      } else if (class_ == CapabilityStatement.class) {
2767        res.addAll((List<T>) capstmts.getList());
2768      } else if (class_ == Measure.class) {
2769        res.addAll((List<T>) measures.getList());
2770      } else if (class_ == Library.class) {
2771        res.addAll((List<T>) libraries.getList());
2772      } else if (class_ == StructureDefinition.class) {
2773        res.addAll((List<T>) structures.getList());
2774      } else if (class_ == StructureMap.class) {
2775        res.addAll((List<T>) transforms.getList());
2776      } else if (class_ == ValueSet.class) {
2777        res.addAll((List<T>) valueSets.getList());
2778      } else if (class_ == CodeSystem.class) {
2779        res.addAll((List<T>) codeSystems.getList());
2780      } else if (class_ == NamingSystem.class) {
2781        res.addAll((List<T>) systems.getList());
2782      } else if (class_ == ActorDefinition.class) {
2783        res.addAll((List<T>) actors.getList());
2784      } else if (class_ == Requirements.class) {
2785        res.addAll((List<T>) requirements.getList());
2786      } else if (class_ == ConceptMap.class) {
2787        res.addAll((List<T>) maps.getList());
2788      } else if (class_ == PlanDefinition.class) {
2789        res.addAll((List<T>) plans.getList());
2790      } else if (class_ == OperationDefinition.class) {
2791        res.addAll((List<T>) operations.getList());
2792      } else if (class_ == Questionnaire.class) {
2793        res.addAll((List<T>) questionnaires.getList());
2794      } else if (class_ == SearchParameter.class) {
2795        res.addAll((List<T>) searchParameters.getList());
2796      }
2797    }
2798    return res;
2799  }
2800
2801  private Set<String> notCanonical = new HashSet<String>();
2802
2803  protected IWorkerContextManager.IPackageLoadingTracker packageTracker;
2804  private boolean forPublication;
2805  private boolean cachingAllowed = true;
2806  private static boolean nsFailHasFailed;
2807
2808  public Resource fetchResourceById(String type, String uri, FhirPublication fhirVersion) {
2809    return fetchResourceById(type, uri);
2810  }
2811  
2812  @Override
2813  public Resource fetchResourceById(String type, String uri) {
2814    synchronized (lock) {
2815      String[] parts = uri.split("\\/");
2816      if (!Utilities.noString(type) && parts.length == 1) {
2817        if (allResourcesById.containsKey(type)) {
2818          ResourceProxy res = allResourcesById.get(type).get(parts[0]);
2819          return res == null ? null : res.getResource();
2820        } else {
2821          return null;
2822        }
2823      }
2824      if (parts.length >= 2) {
2825        if (!Utilities.noString(type)) {
2826          if (!type.equals(parts[parts.length-2])) { 
2827            throw new Error(formatMessage(I18nConstants.RESOURCE_TYPE_MISMATCH_FOR___, type, uri));
2828          }
2829        }
2830        return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]).getResource();
2831      } else {
2832        throw new Error(formatMessage(I18nConstants.UNABLE_TO_PROCESS_REQUEST_FOR_RESOURCE_FOR___, type, uri));
2833      }
2834    }
2835  }
2836
2837  public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource sourceForReference) {
2838    try {
2839      return fetchResourceWithException(class_, uri, sourceForReference);
2840    } catch (FHIRException e) {
2841      throw new Error(e);
2842    }    
2843  }
2844  
2845  public <T extends Resource> T fetchResource(Class<T> class_, String uri, FhirPublication fhirVersion) {
2846    return fetchResource(class_, uri);
2847  }
2848  
2849  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
2850    try {
2851      return fetchResourceWithException(class_, uri, null);
2852    } catch (FHIRException e) {
2853      throw new Error(e);
2854    }
2855  }
2856
2857  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version, FhirPublication fhirVersion) {
2858    return fetchResource(class_, uri, version);
2859  }
2860  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
2861    try {
2862      return fetchResourceWithExceptionByVersion(class_, uri, version, null);
2863    } catch (FHIRException e) {
2864      throw new Error(e);
2865    }
2866  }
2867  
2868  @Override
2869  public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
2870    try {
2871      return fetchResourceWithException(class_, uri) != null;
2872    } catch (Exception e) {
2873      return false;
2874    }
2875  }
2876
2877  public <T extends Resource> boolean hasResource(String cls, String uri) {
2878    try {
2879      return fetchResourceWithException(cls, uri) != null;
2880    } catch (Exception e) {
2881      return false;
2882    }
2883  }
2884
2885  public <T extends Resource> boolean hasResourceVersion(Class<T> class_, String uri, String version) {
2886    try {
2887      return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
2888    } catch (Exception e) {
2889      return false;
2890    }
2891  }
2892
2893  public <T extends Resource> boolean hasResourceVersion(String cls, String uri, String version) {
2894    try {
2895      return fetchResourceWithExceptionByVersion(cls, uri, version, null) != null;
2896    } catch (Exception e) {
2897      return false;
2898    }
2899  }
2900
2901  @Override
2902  public <T extends Resource> boolean hasResource(Class<T> class_, String uri, FhirPublication fhirVersion) {
2903    try {
2904      return fetchResourceWithException(class_, uri) != null;
2905    } catch (Exception e) {
2906      return false;
2907    }
2908  }
2909
2910  public <T extends Resource> boolean hasResource(String cls, String uri, FhirPublication fhirVersion) {
2911    try {
2912      return fetchResourceWithException(cls, uri) != null;
2913    } catch (Exception e) {
2914      return false;
2915    }
2916  }
2917
2918  public <T extends Resource> boolean hasResourceVersion(Class<T> class_, String uri, String version, FhirPublication fhirVersion) {
2919    try {
2920      return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
2921    } catch (Exception e) {
2922      return false;
2923    }
2924  }
2925
2926  public <T extends Resource> boolean hasResourceVersion(String cls, String uri, String version, FhirPublication fhirVersion) {
2927    try {
2928      return fetchResourceWithExceptionByVersion(cls, uri, version, null) != null;
2929    } catch (Exception e) {
2930      return false;
2931    }
2932  }
2933
2934  public <T extends Resource> boolean hasResource(Class<T> class_, String uri, Resource sourceOfReference) {
2935    try {
2936      return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
2937    } catch (Exception e) {
2938      return false;
2939    }
2940  }
2941
2942  public void reportStatus(JsonObject json) {
2943    synchronized (lock) {
2944      json.addProperty("codeystem-count", codeSystems.size());
2945      json.addProperty("valueset-count", valueSets.size());
2946      json.addProperty("conceptmap-count", maps.size());
2947      json.addProperty("transforms-count", transforms.size());
2948      json.addProperty("structures-count", structures.size());
2949      json.addProperty("guides-count", guides.size());
2950      json.addProperty("statements-count", capstmts.size());
2951      json.addProperty("measures-count", measures.size());
2952      json.addProperty("libraries-count", libraries.size());
2953    }
2954  }
2955
2956
2957  public void dropResource(Resource r) throws FHIRException {
2958    dropResource(r.fhirType(), r.getId());   
2959  }
2960
2961  public void dropResource(String fhirType, String id) {
2962    synchronized (lock) {
2963
2964      Map<String, ResourceProxy> map = allResourcesById.get(fhirType);
2965      if (map == null) {
2966        map = new HashMap<String, ResourceProxy>();
2967        allResourcesById.put(fhirType, map);
2968      }
2969      if (map.containsKey(id)) {
2970        map.remove(id); // this is a challenge because we might have more than one resource with this id (different versions)
2971      }
2972
2973      if (fhirType.equals("StructureDefinition")) {
2974        structures.drop(id);
2975        typeManager.reload();
2976      } else if (fhirType.equals("ImplementationGuide")) {
2977        guides.drop(id);
2978      } else if (fhirType.equals("CapabilityStatement")) {
2979        capstmts.drop(id);
2980      } else if (fhirType.equals("Measure")) {
2981        measures.drop(id);
2982      } else if (fhirType.equals("Library")) {
2983        libraries.drop(id);
2984      } else if (fhirType.equals("ValueSet")) {
2985        valueSets.drop(id);
2986      } else if (fhirType.equals("CodeSystem")) {
2987        codeSystems.drop(id);
2988      } else if (fhirType.equals("OperationDefinition")) {
2989        operations.drop(id);
2990      } else if (fhirType.equals("Questionnaire")) {
2991        questionnaires.drop(id);
2992      } else if (fhirType.equals("ConceptMap")) {
2993        maps.drop(id);
2994      } else if (fhirType.equals("StructureMap")) {
2995        transforms.drop(id);
2996      } else if (fhirType.equals("NamingSystem")) {
2997        systems.drop(id);
2998        systemUrlMap = null;
2999      } else if (fhirType.equals("ActorDefinition")) {
3000        actors.drop(id);
3001      } else if (fhirType.equals("Requirements")) {
3002        requirements.drop(id);
3003      }
3004    }
3005  }
3006
3007  private <T extends CanonicalResource> void dropMetadataResource(Map<String, T> map, String id) {
3008    T res = map.get(id);
3009    if (res != null) {
3010      map.remove(id);
3011      if (map.containsKey(res.getUrl())) {
3012        map.remove(res.getUrl());
3013      }
3014      if (res.getVersion() != null) {
3015        if (map.containsKey(res.getUrl()+"|"+res.getVersion())) {
3016          map.remove(res.getUrl()+"|"+res.getVersion());
3017        }
3018      }
3019    }
3020  }
3021
3022  
3023  public String listSupportedSystems() {
3024    synchronized (lock) {
3025      String sl = null;
3026      for (String s : supportedCodeSystems) {
3027        sl = sl == null ? s : sl + "\r\n" + s;
3028      }
3029      return sl;
3030    }
3031  }
3032
3033
3034  public int totalCount() {
3035    synchronized (lock) {
3036      return valueSets.size() +  maps.size() + structures.size() + transforms.size();
3037    }
3038  }
3039  
3040  public List<ConceptMap> listMaps() {
3041    List<ConceptMap> m = new ArrayList<ConceptMap>();
3042    synchronized (lock) {
3043      maps.listAll(m);
3044    }
3045    return m;
3046  }
3047  
3048  public List<StructureDefinition> listStructures() {
3049    List<StructureDefinition> m = new ArrayList<StructureDefinition>();
3050    synchronized (lock) {
3051      structures.listAll(m);    
3052    }
3053    return m;
3054  }
3055
3056  public StructureDefinition getStructure(String code) {
3057    synchronized (lock) {
3058      return structures.get(code);
3059    }
3060  }
3061
3062  private String getUri(NamingSystem ns) {
3063    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
3064      if (id.getType() == NamingSystemIdentifierType.URI) {
3065        return id.getValue();
3066      }
3067    }
3068    return null;
3069  }
3070
3071  private boolean hasOid(NamingSystem ns, String oid) {
3072    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
3073      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) {
3074        return true;
3075      }
3076    }
3077    return false;
3078  }
3079
3080  public void cacheVS(JsonObject json, Map<String, ValidationResult> t) {
3081    synchronized (lock) {
3082      validationCache.put(json.get("url").getAsString(), t);
3083    }
3084  }
3085
3086  public SearchParameter getSearchParameter(String code) {
3087    synchronized (lock) {
3088      return searchParameters.get(code);
3089    }
3090  }
3091
3092  @Override
3093  public org.hl7.fhir.r5.context.ILoggingService getLogger() {
3094    return logger;
3095  }
3096
3097
3098  public StructureDefinition fetchTypeDefinition(String typeName, FhirPublication fhirVersion) {
3099    return fetchTypeDefinition(typeName);
3100  }
3101
3102  @Override
3103  public StructureDefinition fetchTypeDefinition(String typeName) {
3104    if (Utilities.isAbsoluteUrl(typeName)) {
3105      StructureDefinition res = fetchResource(StructureDefinition.class, typeName);
3106      if (res != null) {
3107        return res;
3108      }
3109    } 
3110    StructureDefinition structureDefinition = typeManager.fetchTypeDefinition(typeName);
3111    generateSnapshot(structureDefinition, "5");
3112    return structureDefinition;
3113  }
3114
3115  void generateSnapshot(StructureDefinition structureDefinition, String breadcrumb) {
3116    if (structureDefinition != null && !structureDefinition.isGeneratedSnapshot()) {
3117      if (structureDefinition.isGeneratingSnapshot()) {
3118        throw new FHIRException("Attempt to fetch the profile "+ structureDefinition.getVersionedUrl()+" while generating the snapshot for it");
3119      }
3120      try {
3121        if (logger.isDebugLogging()) {
3122          System.out.println("Generating snapshot for "+ structureDefinition.getVersionedUrl());
3123        }
3124       // structureDefinition.setGeneratingSnapshot(true);
3125        try {
3126          new ContextUtilities(this).generateSnapshot(structureDefinition);
3127        } finally {
3128          //structureDefinition.setGeneratingSnapshot(false);
3129        }
3130      } catch (Exception e) {
3131        // not sure what to do in this case?
3132        System.out.println("Unable to generate snapshot in @" + breadcrumb + " for " + structureDefinition.getVersionedUrl()+": "+e.getMessage());
3133        if (logger.isDebugLogging()) {
3134          e.printStackTrace();
3135        }
3136      }
3137    }
3138  }
3139
3140  @Override
3141  public List<StructureDefinition> fetchTypeDefinitions(String typeName) {
3142    return typeManager.getDefinitions(typeName);
3143  }
3144
3145  @Override
3146  public List<StructureDefinition> fetchTypeDefinitions(String typeName, FhirPublication fhirVersion) {
3147    return typeManager.getDefinitions(typeName);
3148  }
3149
3150
3151  public boolean isPrimitiveType(String type) {
3152    return typeManager.isPrimitive(type);
3153  }
3154
3155  public boolean isDataType(String type) {
3156    return typeManager.isDataType(type);
3157  }
3158  
3159  public boolean isTlogging() {
3160    return tlogging;
3161  }
3162
3163  public void setTlogging(boolean tlogging) {
3164    this.tlogging = tlogging;
3165  }
3166
3167  public UcumService getUcumService() {
3168    return ucumService;
3169  }
3170
3171  public void setUcumService(UcumService ucumService) {
3172    this.ucumService = ucumService;
3173  }
3174
3175  public String getLinkForUrl(String corePath, String url) {
3176    if (url == null) {
3177      return null;
3178    }
3179    
3180    if (codeSystems.has(url)) {
3181      return codeSystems.get(url).getWebPath();
3182    }
3183
3184    if (valueSets.has(url)) {
3185      return valueSets.get(url).getWebPath();
3186    }
3187
3188    if (maps.has(url)) {
3189      return maps.get(url).getWebPath();
3190    }
3191    
3192    if (transforms.has(url)) {
3193      return transforms.get(url).getWebPath();
3194    }
3195    
3196    if (actors.has(url)) {
3197      return actors.get(url).getWebPath();
3198    }
3199    
3200    if (requirements.has(url)) {
3201      return requirements.get(url).getWebPath();
3202    }
3203    
3204    if (structures.has(url)) {
3205      return structures.get(url).getWebPath();
3206    }
3207    
3208    if (guides.has(url)) {
3209      return guides.get(url).getWebPath();
3210    }
3211    
3212    if (capstmts.has(url)) {
3213      return capstmts.get(url).getWebPath();
3214    }
3215    
3216    if (measures.has(url)) {
3217      return measures.get(url).getWebPath();
3218    }
3219
3220    if (libraries.has(url)) {
3221      return libraries.get(url).getWebPath();
3222    }
3223
3224    if (searchParameters.has(url)) {
3225      return searchParameters.get(url).getWebPath();
3226    }
3227        
3228    if (questionnaires.has(url)) {
3229      return questionnaires.get(url).getWebPath();
3230    }
3231
3232    if (operations.has(url)) {
3233      return operations.get(url).getWebPath();
3234    }
3235    
3236    if (plans.has(url)) {
3237      return plans.get(url).getWebPath();
3238    }
3239
3240    if (url.equals("http://loinc.org")) {
3241      return corePath+"loinc.html";
3242    }
3243    if (url.equals("http://unitsofmeasure.org")) {
3244      return corePath+"ucum.html";
3245    } 
3246    if (url.equals("http://snomed.info/sct")) {
3247      return corePath+"snomed.html";
3248    } 
3249    return null;
3250  }
3251
3252  public List<ImplementationGuide> allImplementationGuides() {
3253    List<ImplementationGuide> res = new ArrayList<>();
3254    guides.listAll(res);
3255    return res;
3256  }
3257
3258  @Override
3259  public Set<String> getBinaryKeysAsSet() { return binaries.keySet(); }
3260
3261  @Override
3262  public boolean hasBinaryKey(String binaryKey) {
3263    return binaries.containsKey(binaryKey);
3264  }
3265
3266  @Override
3267  public byte[] getBinaryForKey(String binaryKey) {
3268    IByteProvider bp = binaries.get(binaryKey);
3269    try {
3270      return bp == null ? null : bp.bytes();
3271    } catch (Exception e) {
3272      throw new FHIRException(e);
3273    }
3274  }
3275
3276  public void finishLoading(boolean genSnapshots) {
3277    if (!hasResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Base")) {
3278      cacheResource(ProfileUtilities.makeBaseDefinition(version));
3279    }
3280    if(genSnapshots) {
3281      for (StructureDefinition sd : listStructures()) {
3282        try {
3283          if (sd.getSnapshot().isEmpty()) { 
3284            new ContextUtilities(this).generateSnapshot(sd);
3285            //          new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
3286          }
3287        } catch (Exception e) {
3288          System.out.println("Unable to generate snapshot @1 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
3289          if (logger.isDebugLogging()) {
3290            e.printStackTrace();          
3291          }
3292        }
3293      }  
3294    }
3295    
3296    codeSystems.setVersion(version);
3297    valueSets.setVersion(version);
3298    maps.setVersion(version);
3299    transforms.setVersion(version);
3300    structures.setVersion(version);
3301    typeManager.reload();
3302    measures.setVersion(version);
3303    libraries.setVersion(version);
3304    guides.setVersion(version);
3305    capstmts.setVersion(version);
3306    searchParameters.setVersion(version);
3307    questionnaires.setVersion(version);
3308    operations.setVersion(version);
3309    plans.setVersion(version);
3310    systems.setVersion(version);
3311    actors.setVersion(version);
3312    requirements.setVersion(version);
3313  }
3314
3315  protected String tail(String url) {
3316    if (Utilities.noString(url)) {
3317      return "noname";
3318    }
3319    if (url.contains("/")) {
3320      return url.substring(url.lastIndexOf("/")+1);
3321    }
3322    return url;
3323  }
3324  
3325  public int getClientRetryCount() {
3326    return terminologyClientManager.getRetryCount();
3327  }
3328  
3329  public IWorkerContext setClientRetryCount(int value) {
3330    terminologyClientManager.setRetryCount(value);
3331    return this;
3332  }
3333
3334  public TerminologyClientManager getTxClientManager() {
3335    return terminologyClientManager;
3336  }
3337
3338  public String getCacheId() {
3339    return terminologyClientManager.getCacheId();
3340  }
3341
3342  public TimeTracker clock() {
3343    return clock;
3344  }
3345 
3346  public int countAllCaches() {
3347    return codeSystems.size() + valueSets.size() + maps.size() + transforms.size() + structures.size() + measures.size() + libraries.size() + 
3348        guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() + 
3349        systems.size()+ actors.size()+ requirements.size();
3350  }
3351
3352  public Set<String> getCodeSystemsUsed() {
3353    return codeSystemsUsed ;
3354  }
3355 
3356  public IWorkerContextManager.ICanonicalResourceLocator getLocator() {
3357    return locator;
3358  }
3359
3360  public void setLocator(IWorkerContextManager.ICanonicalResourceLocator locator) {
3361    this.locator = locator;
3362  }
3363
3364  public String getUserAgent() {
3365    return userAgent;
3366  }
3367
3368  protected void setUserAgent(String userAgent) {
3369    this.userAgent = userAgent;
3370    terminologyClientManager.setUserAgent(userAgent);
3371  }
3372
3373
3374  public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() {
3375    return packageTracker;
3376  }
3377  
3378  public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) {
3379    this.packageTracker = packageTracker;
3380    return this;
3381  }
3382  
3383
3384  @Override
3385  public PEBuilder getProfiledElementBuilder(PEElementPropertiesPolicy elementProps, boolean fixedProps) {
3386    // TODO Auto-generated method stub
3387    return new PEBuilder(this, elementProps, fixedProps);
3388  }
3389  
3390  public boolean isForPublication() {
3391    return forPublication;
3392  }
3393  
3394  public void setForPublication(boolean value) {
3395    forPublication = value;
3396  }
3397
3398  public boolean isCachingAllowed() {
3399    return cachingAllowed;
3400  }
3401
3402  public void setCachingAllowed(boolean cachingAllowed) {
3403    this.cachingAllowed = cachingAllowed;
3404  }
3405
3406  @Override
3407  public OIDSummary urlsForOid(String oid, String resourceType) {
3408    OIDSummary set = urlsForOid(oid, resourceType, true);
3409    if (set.getDefinitions().size() > 1) {
3410      set = urlsForOid(oid, resourceType, false);
3411    }
3412    return set;
3413  }
3414  
3415  public OIDSummary urlsForOid(String oid, String resourceType, boolean retired) {
3416    OIDSummary summary = new OIDSummary();
3417    if (oid != null) {
3418      if (oidCacheManual.containsKey(oid)) {
3419        summary.addOIDs(oidCacheManual.get(oid));
3420      }
3421      for (OIDSource os : oidSources) {
3422        if (os.db == null) {
3423          os.db = connectToOidSource(os.folder);
3424        }
3425        if (os.db != null) {
3426          try {
3427            PreparedStatement psql = resourceType == null ?
3428                os.db.prepareStatement("Select TYPE, URL, VERSION, Status from OIDMap where OID = ?") :
3429                os.db.prepareStatement("Select TYPE, URL, VERSION, Status from OIDMap where TYPE = '"+resourceType+"' and OID = ?");
3430            psql.setString(1, oid);
3431            ResultSet rs = psql.executeQuery();
3432            while (rs.next()) {
3433              if (retired || !"retired".equals(rs.getString(4))) {
3434                String rt = rs.getString(1);
3435                String url = rs.getString(2);
3436                String version = rs.getString(3);
3437                String status = rs.getString(4);
3438                summary.addOID(new OIDDefinition(rt, oid, url, version, os.pid, status));
3439              }
3440            }
3441          } catch (Exception e) {
3442            // nothing, there would alreagy have been an error
3443  //          e.printStackTrace();
3444          }
3445        }
3446      }      
3447  
3448      switch (oid) {
3449      case "2.16.840.1.113883.6.1" :
3450        summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.1", "http://loinc.org", null, null, null));
3451        break;
3452      case "2.16.840.1.113883.6.8" :
3453        summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.8", "http://unitsofmeasure.org", null, null, null));
3454        break;
3455      case "2.16.840.1.113883.6.96" :
3456        summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.96", "http://snomed.info/sct", null, null, null));
3457        break;
3458      default:
3459      }
3460    }
3461    summary.sort();
3462    return summary;
3463  }
3464
3465  private Connection connectToOidSource(String folder) {
3466    try {
3467      File ff = ManagedFileAccess.file(folder);
3468      File of = ManagedFileAccess.file(Utilities.path(ff.getAbsolutePath(), ".oid-map-2.db"));
3469      if (!of.exists()) {
3470        OidIndexBuilder oidBuilder = new OidIndexBuilder(ff, of);
3471        oidBuilder.build();
3472      }
3473      return DriverManager.getConnection("jdbc:sqlite:"+of.getAbsolutePath());
3474    } catch (Exception e) {
3475      return null;
3476    }
3477  }
3478
3479
3480  public void unload() {
3481
3482    codeSystems.unload();
3483    valueSets.unload();
3484    maps.unload();
3485    transforms.unload();
3486    structures.unload();
3487    typeManager.unload();
3488    measures.unload();
3489    libraries.unload();
3490    guides.unload();
3491    capstmts.unload();
3492    searchParameters.unload();
3493    questionnaires.unload();
3494    operations.unload();
3495    plans.unload();
3496    actors.unload();
3497    requirements.unload();
3498    systems.unload();
3499
3500    binaries.clear();
3501    validationCache.clear();
3502    txCache.unload();
3503}
3504  
3505  private <T extends Resource> T doFindTxResource(Class<T> class_, String canonical) {
3506    // well, we haven't found it locally. We're going look it up
3507    if (class_ == ValueSet.class) {
3508      SourcedValueSet svs = null;
3509      if (txCache.hasValueSet(canonical)) {
3510        svs = txCache.getValueSet(canonical);
3511      } else {
3512        svs = terminologyClientManager.findValueSetOnServer(canonical);
3513        txCache.cacheValueSet(canonical, svs);
3514      }
3515      if (svs != null) {
3516        String web = ToolingExtensions.readStringExtension(svs.getVs(), ToolingExtensions.EXT_WEB_SOURCE);
3517        if (web == null) {
3518          web = Utilities.pathURL(svs.getServer(), "ValueSet", svs.getVs().getIdBase());
3519        }
3520        svs.getVs().setWebPath(web);
3521        svs.getVs().setUserData(UserDataNames.render_external_link, svs.getServer()); // so we can render it differently
3522      }      
3523      if (svs == null) {
3524        return null;
3525      } else {
3526        cacheResource(svs.getVs());
3527        return (T) svs.getVs();
3528      }
3529    } else if (class_ == CodeSystem.class) {
3530      SourcedCodeSystem scs = null;
3531      if (txCache.hasCodeSystem(canonical)) {
3532        scs = txCache.getCodeSystem(canonical);
3533      } else {
3534        scs = terminologyClientManager.findCodeSystemOnServer(canonical);
3535        txCache.cacheCodeSystem(canonical, scs);
3536      }
3537      if (scs != null) {
3538        String web = ToolingExtensions.readStringExtension(scs.getCs(), ToolingExtensions.EXT_WEB_SOURCE);
3539        if (web == null) {
3540          web = Utilities.pathURL(scs.getServer(), "ValueSet", scs.getCs().getIdBase());
3541        }
3542        scs.getCs().setWebPath(web);
3543        scs.getCs().setUserData(UserDataNames.render_external_link, scs.getServer()); // so we can render it differently
3544      }      
3545      if (scs == null) {
3546        return null;
3547      } else {
3548        cacheResource(scs.getCs());
3549        return (T) scs.getCs();
3550      }
3551    } else {
3552      throw new Error("Not supported: doFindTxResource with type of "+class_.getName());
3553    }
3554  }
3555
3556  public <T extends Resource> T findTxResource(Class<T> class_, String canonical, Resource sourceOfReference) {
3557    if (canonical == null) {
3558      return null;
3559    }
3560   T result = fetchResource(class_, canonical, sourceOfReference);
3561   if (result == null) {
3562     result = doFindTxResource(class_, canonical);
3563   }
3564   return result;
3565  }
3566
3567  public <T extends Resource> T findTxResource(Class<T> class_, String canonical) {
3568    if (canonical == null) {
3569      return null;
3570    }
3571    T result = fetchResource(class_, canonical);
3572    if (result == null) {
3573      result = doFindTxResource(class_, canonical);
3574    }
3575    return result;
3576  }
3577  
3578  public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version) {
3579    if (canonical == null) {
3580      return null;
3581    }
3582    T result = fetchResource(class_, canonical, version);
3583    if (result == null) {
3584      result = doFindTxResource(class_, canonical+"|"+version);
3585    }
3586    return result;
3587  }
3588
3589  @Override
3590  public <T extends Resource> List<T> fetchResourcesByUrl(Class<T> class_, String uri) {
3591    List<T> res = new ArrayList<>();
3592    if (uri != null && !uri.startsWith("#")) {
3593      if (class_ == StructureDefinition.class) {
3594        uri = ProfileUtilities.sdNs(uri, null);
3595      }
3596      assert !uri.contains("|");
3597      if (uri.contains("#")) {
3598        uri = uri.substring(0, uri.indexOf("#"));
3599      } 
3600      synchronized (lock) {
3601        if (class_ == Resource.class || class_ == null) {
3602          List<ResourceProxy> list = allResourcesByUrl.get(uri);
3603          if (list != null) {
3604            for (ResourceProxy r : list) {
3605              if (uri.equals(r.getUrl())) {
3606                res.add((T) r.getResource());
3607              }
3608            }            
3609          }  
3610        }
3611        if (class_ == ImplementationGuide.class || class_ == Resource.class || class_ == null) {
3612          for (ImplementationGuide cr : guides.getForUrl(uri)) {
3613            res.add((T) cr);
3614          } 
3615        } else if (class_ == CapabilityStatement.class || class_ == Resource.class || class_ == null) {
3616          for (CapabilityStatement cr : capstmts.getForUrl(uri)) {
3617            res.add((T) cr);
3618          } 
3619        } else if (class_ == Measure.class || class_ == Resource.class || class_ == null) {
3620          for (Measure cr : measures.getForUrl(uri)) {
3621            res.add((T) cr);
3622          } 
3623        } else if (class_ == Library.class || class_ == Resource.class || class_ == null) {
3624          for (Library cr : libraries.getForUrl(uri)) {
3625            res.add((T) cr);
3626          } 
3627        } else if (class_ == StructureDefinition.class || class_ == Resource.class || class_ == null) {
3628          for (StructureDefinition cr : structures.getForUrl(uri)) {
3629            res.add((T) cr);
3630          } 
3631        } else if (class_ == StructureMap.class || class_ == Resource.class || class_ == null) {
3632          for (StructureMap cr : transforms.getForUrl(uri)) {
3633            res.add((T) cr);
3634          } 
3635        } else if (class_ == NamingSystem.class || class_ == Resource.class || class_ == null) {
3636          for (NamingSystem cr : systems.getForUrl(uri)) {
3637            res.add((T) cr);
3638          } 
3639        } else if (class_ == ValueSet.class || class_ == Resource.class || class_ == null) {
3640          for (ValueSet cr : valueSets.getForUrl(uri)) {
3641            res.add((T) cr);
3642          } 
3643        } else if (class_ == CodeSystem.class || class_ == Resource.class || class_ == null) {
3644          for (CodeSystem cr : codeSystems.getForUrl(uri)) {
3645            res.add((T) cr);
3646          } 
3647        } else if (class_ == ConceptMap.class || class_ == Resource.class || class_ == null) {
3648          for (ConceptMap cr : maps.getForUrl(uri)) {
3649            res.add((T) cr);
3650          } 
3651        } else if (class_ == ActorDefinition.class || class_ == Resource.class || class_ == null) {
3652          for (ActorDefinition cr : actors.getForUrl(uri)) {
3653            res.add((T) cr);
3654          } 
3655        } else if (class_ == Requirements.class || class_ == Resource.class || class_ == null) {
3656          for (Requirements cr : requirements.getForUrl(uri)) {
3657            res.add((T) cr);
3658          } 
3659        } else if (class_ == PlanDefinition.class || class_ == Resource.class || class_ == null) {
3660          for (PlanDefinition cr : plans.getForUrl(uri)) {
3661            res.add((T) cr);
3662          } 
3663        } else if (class_ == OperationDefinition.class || class_ == Resource.class || class_ == null) {
3664          for (OperationDefinition cr : operations.getForUrl(uri)) {
3665            res.add((T) cr);
3666          } 
3667        } else if (class_ == Questionnaire.class || class_ == Resource.class || class_ == null) {
3668          for (Questionnaire cr : questionnaires.getForUrl(uri)) {
3669            res.add((T) cr);
3670          } 
3671        } else if (class_ == SearchParameter.class || class_ == Resource.class || class_ == null) {
3672          for (SearchParameter cr : searchParameters.getForUrl(uri)) {
3673            res.add((T) cr);
3674          } 
3675        }
3676      }
3677    }
3678    return res;
3679  }
3680  
3681  public void setLocale(Locale locale) {
3682    super.setLocale(locale);
3683    if (locale != null) {
3684      String lt = locale.toLanguageTag();
3685      if ("und".equals(lt)) {
3686        throw new FHIRException("The locale "+locale.toString()+" is not valid");
3687      }
3688      if (expParameters != null) {
3689        for (ParametersParameterComponent p : expParameters.getParameter()) {
3690          if ("displayLanguage".equals(p.getName())) {
3691            if (p.hasUserData(UserDataNames.auto_added_parameter)) {
3692              p.setValue(new CodeType(lt));
3693              return;
3694            } else {
3695              // user supplied, we leave it alone
3696              return ;
3697            }
3698          }
3699        }
3700        ParametersParameterComponent p = expParameters.addParameter();
3701        p.setName("displayLanguage");
3702        p.setValue(new CodeType(lt));
3703        p.setUserData(UserDataNames.auto_added_parameter, true);
3704      }
3705    }
3706  }
3707}