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