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