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);
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()));
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 {
1873        set.add(TerminologyClientManager.UNRESOLVED_VALUESET);
1874      }
1875    }
1876  }
1877
1878  private void findRelevantSystems(Set<String> set, Coding c) {
1879    if (c.hasSystem()) {
1880      if (c.hasVersion()) {
1881        set.add(c.getSystem()+"|"+c.getVersion());
1882      } else {
1883        set.add(c.getSystem());
1884      }
1885    }    
1886  }
1887
1888  protected ValidationResult validateOnServer(TerminologyClientContext tc, ValueSet vs, Parameters pin, ValidationOptions options) throws FHIRException {
1889    return  validateOnServer2(tc, vs, pin, options, null);
1890  }
1891  
1892  protected ValidationResult validateOnServer2(TerminologyClientContext tc, ValueSet vs, Parameters pin, ValidationOptions options, Set<String> systems) throws FHIRException {
1893
1894    if (vs != null) {
1895      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1896        codeSystemsUsed.add(inc.getSystem());
1897      }
1898      for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1899        codeSystemsUsed.add(inc.getSystem());
1900      }
1901    }
1902
1903    addServerValidationParameters(null, tc, vs, pin, options, systems);
1904    
1905    if (txLog != null) {
1906      txLog.clearLastId();
1907    }
1908    if (tc == null) {
1909      throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1910    }
1911    Parameters pOut;
1912    if (vs == null) {
1913      pOut = tc.getClient().validateCS(pin);
1914    } else {
1915      pOut = tc.getClient().validateVS(pin);
1916    }
1917    return processValidationResult(pOut, vs == null ? null : vs.getUrl(), tc.getClient().getAddress());
1918  }
1919
1920  protected void addServerValidationParameters(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options) {
1921    addServerValidationParameters(opCtxt, terminologyClientContext, vs, pin, options, null);
1922  }
1923  
1924  protected void addServerValidationParameters(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options, Set<String> systems) {
1925    boolean cache = false;
1926    if (vs != null) {
1927      if (terminologyClientContext != null && terminologyClientContext.isTxCaching() && terminologyClientContext.getCacheId() != null && vs.getUrl() != null && terminologyClientContext.getCached().contains(vs.getUrl()+"|"+ vs.getVersion())) {
1928        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
1929        if (vs.hasVersion()) {
1930          pin.addParameter().setName("valueSetVersion").setValue(new StringType(vs.getVersion()));            
1931        }
1932      } else if (options.getVsAsUrl()){
1933        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
1934      } else {
1935        if (vs.hasCompose() && vs.hasExpansion()) {
1936          vs = vs.copy();
1937          vs.setExpansion(null);
1938        }
1939        pin.addParameter().setName("valueSet").setResource(vs);
1940        if (vs.getUrl() != null) {
1941          terminologyClientContext.getCached().add(vs.getUrl()+"|"+ vs.getVersion());
1942        }
1943      }
1944      cache = true;
1945      addDependentResources(opCtxt, terminologyClientContext, pin, vs);
1946    }
1947    if (systems != null) {
1948      for (String s : systems) {
1949        cache = addDependentCodeSystem(opCtxt, terminologyClientContext, pin, s, null) || cache;
1950      }
1951    }
1952    pin.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
1953    for (ParametersParameterComponent pp : pin.getParameter()) {
1954      if (pp.getName().equals("profile")) {
1955        throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT));
1956      }
1957    }
1958    if (expansionParameters.get() == null) {
1959      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1960    }
1961    String defLang = null;
1962    for (ParametersParameterComponent pp : expansionParameters.get().getParameter()) {
1963      if ("defaultDisplayLanguage".equals(pp.getName())) {
1964        defLang = pp.getValue().primitiveValue();
1965      } else if (!pin.hasParameter(pp.getName())) {
1966        pin.addParameter(pp);
1967      } else if ("displayLanguage".equals(pp.getName())) {
1968        pin.setParameter(pp);
1969      }
1970    }
1971    if (defLang != null && !pin.hasParameter("displayLanguage")) {
1972      pin.addParameter().setName("displayLanguage").setValue(new CodeType(defLang));
1973    }
1974
1975    if (options.isDisplayWarningMode()) {
1976      pin.addParameter("mode","lenient-display-validation");
1977    }
1978    pin.addParameter("diagnostics", true);
1979  }
1980
1981  private boolean addDependentResources(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ValueSet vs) {
1982    boolean cache = false;
1983    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1984      cache = addDependentResources(opCtxt, tc, pin, inc, vs) || cache;
1985    }
1986    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1987      cache = addDependentResources(opCtxt, tc, pin, inc, vs) || cache;
1988    }
1989    return cache;
1990  }
1991
1992  private boolean addDependentResources(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ConceptSetComponent inc, Resource src) {
1993    boolean cache = false;
1994    for (CanonicalType c : inc.getValueSet()) {
1995      ValueSet vs = fetchResource(ValueSet.class, c.getValue(), null, src);
1996      if (vs != null && !hasCanonicalResource(pin, "tx-resource", vs.getVUrl())) {
1997        cache = checkAddToParams(tc, pin, vs) || cache;
1998        addDependentResources(opCtxt, tc, pin, vs);
1999        for (Extension ext : vs.getExtensionsByUrl(ExtensionDefinitions.EXT_VS_CS_SUPPL_NEEDED)) {
2000          if (ext.hasValueCanonicalType()) {
2001            String url = ext.getValueCanonicalType().asStringValue();
2002            CodeSystem supp = fetchResource(CodeSystem.class, url);
2003            if (supp != null) {
2004              if (opCtxt != null) {
2005                opCtxt.seeSupplement(supp);
2006              }
2007              cache = checkAddToParams(tc, pin, supp) || cache;            
2008            }
2009          }
2010        }
2011      }
2012    }
2013    String sys = inc.getSystem();
2014    cache = addDependentCodeSystem(opCtxt, tc, pin, sys, src) || cache;
2015    return cache;
2016  }
2017
2018  public boolean addDependentCodeSystem(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, String sys, Resource src) {
2019    boolean cache = false;
2020    CodeSystem cs = fetchResource(CodeSystem.class, sys, null, src);
2021    if (cs != null && !hasCanonicalResource(pin, "tx-resource", cs.getVUrl()) && (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
2022      cache = checkAddToParams(tc, pin, cs) || cache;
2023    }
2024    for (CodeSystem supp : codeSystems.getSupplements(cs)) {
2025      if (opCtxt != null) {
2026        opCtxt.seeSupplement(supp);
2027      }
2028      if (!hasCanonicalResource(pin, "tx-resource", supp.getVUrl()) ) {
2029        cache = checkAddToParams(tc, pin, supp) || cache;
2030      }
2031    }
2032    if (sys != null) {
2033      // we also have to look at this by version because the resource might not be versioned or we might not have a copy
2034      for (CodeSystem supp : codeSystems.getSupplements(sys)) {
2035
2036        if (opCtxt != null) {
2037          opCtxt.seeSupplement(supp);
2038        }
2039        if (!hasCanonicalResource(pin, "tx-resource", supp.getVUrl()) ) {
2040          cache = checkAddToParams(tc, pin, supp) || cache;
2041        }
2042      }
2043      if (!sys.contains("!")) {
2044        sys = getFixedVersion(sys, pin);
2045        if (sys != null) {
2046          for (CodeSystem supp : codeSystems.getSupplements(sys)) {
2047            if (opCtxt != null) {
2048              opCtxt.seeSupplement(supp);
2049            }
2050            if (!hasCanonicalResource(pin, "tx-resource", supp.getVUrl()) ) {
2051              cache = checkAddToParams(tc, pin, supp) || cache;
2052            }
2053          }
2054        }
2055      }
2056    }
2057    return cache;
2058  }
2059
2060  private String getFixedVersion(String sys, Parameters pin) {
2061    for (ParametersParameterComponent p : pin.getParameter()) {
2062      if (Utilities.existsInList(p.getName(), "system-version", "force-system-version", "default-system-version")) {
2063        if (p.hasValuePrimitive() && p.getValue().primitiveValue() != null && p.getValue().primitiveValue().startsWith(sys)) {
2064          return p.getValue().primitiveValue();
2065        }
2066      }
2067    }
2068    return null;
2069  }
2070
2071  private boolean checkAddToParams(TerminologyClientContext tc, Parameters pin, CanonicalResource cr) {
2072    boolean cache = false;
2073    boolean addToParams = false;
2074    if (tc.usingCache()) {
2075      if (!tc.alreadyCached(cr)) {
2076        tc.addToCache(cr);
2077
2078        logger.logDebugMessage(LogCategory.CONTEXT, "add to cache: "+cr.getVUrl());
2079
2080        addToParams = true;
2081        cache = true;
2082      } else {
2083        logger.logDebugMessage(LogCategory.CONTEXT,"already cached: "+cr.getVUrl());
2084      }
2085    } else {
2086      addToParams = true;
2087    }
2088    if (addToParams) {
2089      pin.addParameter().setName("tx-resource").setResource(cr);
2090    }
2091    return cache;
2092  }
2093
2094  private boolean hasCanonicalResource(Parameters pin, String name, String vUrl) {
2095    for (ParametersParameterComponent p : pin.getParameter()) {
2096      if (name.equals(p.getName()) && p.hasResource() &&
2097          p.getResource() instanceof CanonicalResource && vUrl.equals(((CanonicalResource) p.getResource()).getVUrl())) {
2098        return true;
2099      }
2100    }
2101    return false;
2102  }
2103
2104  public ValidationResult processValidationResult(Parameters pOut, String vs, String server) {
2105    boolean ok = false;
2106    String message = "No Message returned";
2107    String display = null;
2108    String system = null;
2109    String code = null;
2110    String version = null;
2111    boolean inactive = false;
2112    String status = null;
2113    String diagnostics = null;
2114    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
2115    Set<String> unknownSystems = new HashSet<>();
2116
2117    TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN;
2118    for (ParametersParameterComponent p : pOut.getParameter()) {
2119      if (p.hasValue()) {
2120        if (p.getName().equals("result")) {
2121          ok = ((BooleanType) p.getValue()).getValue().booleanValue();
2122        } else if (p.getName().equals("message")) {
2123          message = p.getValue().primitiveValue();
2124        } else if (p.getName().equals("display")) {
2125          display = p.getValue().primitiveValue();
2126        } else if (p.getName().equals("system")) {
2127          system = ((PrimitiveType<?>) p.getValue()).asStringValue();
2128        } else if (p.getName().equals("version")) {
2129          version = ((PrimitiveType<?>) p.getValue()).asStringValue();
2130        } else if (p.getName().equals("code")) {
2131          code = ((PrimitiveType<?>) p.getValue()).asStringValue();
2132        } else if (p.getName().equals("diagnostics")) {
2133          diagnostics = ((PrimitiveType<?>) p.getValue()).asStringValue();
2134        } else if (p.getName().equals("inactive")) {
2135          inactive = "true".equals(((PrimitiveType<?>) p.getValue()).asStringValue());
2136        } else if (p.getName().equals("status")) {
2137          status = ((PrimitiveType<?>) p.getValue()).asStringValue();
2138        } else if (p.getName().equals("x-caused-by-unknown-system")) {
2139          String unkSystem = ((PrimitiveType<?>) p.getValue()).asStringValue();
2140          if (unkSystem != null && unkSystem.contains("|")) {
2141            err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED_VERSION; 
2142            system = unkSystem.substring(0, unkSystem.indexOf("|"));
2143            version = unkSystem.substring(unkSystem.indexOf("|")+1);
2144          } else {
2145            err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;            
2146            unknownSystems.add(unkSystem);      
2147          }
2148        } else if (p.getName().equals("x-unknown-system")) {
2149          unknownSystems.add(((PrimitiveType<?>) p.getValue()).asStringValue());      
2150        } else if (p.getName().equals("warning-withdrawn")) {
2151          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2152          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2153          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_WITHDRAWN : I18nConstants.MSG_WITHDRAWN_SRC, msg, vs, impliedType(msg)));              
2154          issues.add(iss);
2155        } else if (p.getName().equals("warning-deprecated")) {
2156          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2157          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2158          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DEPRECATED : I18nConstants.MSG_DEPRECATED_SRC, msg, vs, impliedType(msg)));              
2159          issues.add(iss);
2160        } else if (p.getName().equals("warning-retired")) {
2161          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2162          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2163          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_RETIRED : I18nConstants.MSG_RETIRED_SRC, msg, vs, impliedType(msg)));              
2164          issues.add(iss);
2165        } else if (p.getName().equals("warning-experimental")) {
2166          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2167          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2168          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_EXPERIMENTAL : I18nConstants.MSG_EXPERIMENTAL_SRC, msg, vs, impliedType(msg)));              
2169          issues.add(iss);
2170        } else if (p.getName().equals("warning-draft")) {
2171          String msg = ((PrimitiveType<?>) p.getValue()).asStringValue();
2172          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
2173          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DRAFT : I18nConstants.MSG_DRAFT_SRC, msg, vs, impliedType(msg)));              
2174          issues.add(iss);
2175        } else if (p.getName().equals("cause")) {
2176          try {
2177            IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue());
2178            if (it == IssueType.UNKNOWN) {
2179              err = TerminologyServiceErrorClass.UNKNOWN;
2180            } else if (it == IssueType.NOTFOUND) {
2181              err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
2182            } else if (it == IssueType.NOTSUPPORTED) {
2183              err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED;
2184            } else {
2185              err = null;
2186            }
2187          } catch (FHIRException e) {
2188          }
2189        }
2190      } else if (p.hasResource()) {
2191        if (p.getName().equals("issues")) {
2192          OperationOutcome oo = (OperationOutcome) p.getResource();
2193          for (OperationOutcomeIssueComponent iss : oo.getIssue()) {
2194            iss.addExtension(ExtensionDefinitions.EXT_ISSUE_SERVER, new UrlType(server));
2195            issues.add(iss);
2196          }
2197        } else {
2198          // nothing?
2199        }
2200      }
2201    }
2202    ValidationResult res = null;
2203    if (!ok) {
2204      res = new ValidationResult(IssueSeverity.ERROR, message, err, null).setTxLink(txLog == null ? null : txLog.getLastId());
2205      if (code != null) {
2206        res.setDefinition(new ConceptDefinitionComponent().setDisplay(display).setCode(code));
2207        res.setDisplay(display);
2208      }
2209      if (system != null) {
2210        res.setSystem(system);
2211      }
2212      if (version != null) {
2213        res.setVersion(version);
2214      }
2215    } else if (message != null && !message.equals("No Message returned")) { 
2216      res = new ValidationResult(IssueSeverity.WARNING, message, system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog == null ? null : txLog.getLastId());
2217    } else if (display != null) {
2218      res = new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog == null ? null : txLog.getLastId());
2219    } else {
2220      res = new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog == null ? null : txLog.getLastId());
2221    }
2222    res.setIssues(issues);
2223    res.setStatus(inactive, status);
2224    res.setUnknownSystems(unknownSystems);
2225    res.setServer(server);
2226    res.setParameters(pOut);
2227    res.setDiagnostics(diagnostics);
2228    return res;
2229  }
2230
2231  // --------------------------------------------------------------------------------------------------------------------------------------------------------
2232  
2233  private Object impliedType(String msg) {
2234    if (msg.contains("/CodeSystem")) {
2235      return "CodeSystem";
2236    }
2237    if (msg.contains("/ValueSet")) {
2238      return "ValueSet";
2239    }
2240    return "item";
2241  }
2242
2243  public void initTxCache(String cachePath) throws FileNotFoundException, FHIRException, IOException {
2244    if (cachePath != null) {
2245      txCache = new TerminologyCache(lock, cachePath);
2246      initTxCache(txCache);
2247    }
2248  }
2249  
2250  public void initTxCache(TerminologyCache cache) {
2251    txCache = cache;
2252    terminologyClientManager.setCache(txCache);
2253  }
2254
2255  public void clearTSCache(String url) throws Exception {
2256    txCache.removeCS(url);
2257  }
2258
2259  public boolean isCanRunWithoutTerminology() {
2260    return canRunWithoutTerminology;
2261  }
2262
2263  public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) {
2264    this.canRunWithoutTerminology = canRunWithoutTerminology;
2265  }
2266
2267  public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger) {
2268    this.logger = logger;
2269    getTxClientManager().setLogger(logger);
2270  }
2271
2272  /**
2273   * Returns a copy of the expansion parameters used by this context. Note that because the return value is a copy, any
2274   * changes done to it will not be reflected in the context and any changes to the context will likewise not be
2275   * reflected in the return value after it is returned. If you need to change the expansion parameters, use
2276   * {@link #setExpansionParameters(Parameters)}.
2277   *
2278   * @return a copy of the expansion parameters
2279   */
2280  public Parameters getExpansionParameters() {
2281    final Parameters parameters = expansionParameters.get();
2282    return parameters == null ? null : parameters.copy();
2283  }
2284
2285    public void setExpansionParameters(Parameters expansionParameters) {
2286    this.expansionParameters.set(expansionParameters);
2287    this.terminologyClientManager.setExpansionParameters(expansionParameters);
2288  }
2289
2290  @Override
2291  public boolean isNoTerminologyServer() {
2292    return noTerminologyServer || !terminologyClientManager.hasClient();
2293  }
2294
2295  public void setNoTerminologyServer(boolean noTerminologyServer) {
2296    this.noTerminologyServer = noTerminologyServer;
2297  }
2298
2299  public String getName() {
2300    return name;
2301  }
2302
2303  public void setName(String name) {
2304    this.name = name;
2305  }
2306
2307
2308  public List<String> getResourceNames(FhirPublication fhirVersion) {
2309    return getResourceNames();    
2310  }
2311  
2312  public Set<String> getResourceNamesAsSet(FhirPublication fhirVersion) {
2313    return getResourceNamesAsSet();
2314  }
2315  
2316  @Override
2317  public Set<String> getResourceNamesAsSet() {
2318    Set<String> res = new HashSet<String>();
2319    res.addAll(getResourceNames());
2320    return res;
2321  }
2322
2323  @Override
2324  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
2325    return fetchResourceWithException(class_, uri, null, null);
2326  }
2327
2328  public <T extends Resource> T fetchResourceWithException(String cls, String uri) throws FHIRException {
2329    return fetchResourceWithExceptionByVersion(cls, uri, null, null);
2330  }
2331
2332  public <T extends Resource> T fetchResourceByVersionWithException(Class<T> class_, String uri, String version) throws FHIRException {
2333    return fetchResourceWithExceptionByVersion(class_, uri, version, null);
2334  }
2335
2336  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, String version, Resource sourceForReference) throws FHIRException {
2337    return fetchResourceWithExceptionByVersion(class_, uri, version, sourceForReference);
2338  }
2339  
2340  @SuppressWarnings("unchecked")
2341  public <T extends Resource> T fetchResourceWithExceptionByVersion(Class<T> class_, String uri, String version, Resource sourceForReference) throws FHIRException {
2342    if (uri == null) {
2343      return null;
2344    }
2345    if (uri.startsWith("#")) {
2346      if (sourceForReference != null && sourceForReference instanceof DomainResource) {
2347        for (Resource r : ((DomainResource) sourceForReference).getContained()) {
2348          if (r.getClass() == class_ &&( "#"+r.getIdBase()).equals(uri)) {
2349            if (r instanceof CanonicalResource) {
2350              CanonicalResource cr = (CanonicalResource) r;
2351              if (!cr.hasUrl()) {
2352                cr.setUrl(UUIDUtilities.makeUuidUrn());
2353              }              
2354            }
2355            return (T) r;
2356          }
2357        }
2358      }
2359      return null;
2360    }
2361    
2362    if (QA_CHECK_REFERENCE_SOURCE) {
2363      // it can be tricky to trace the source of a reference correctly. The code isn't water tight,
2364      // particularly around snapshot generation. Enable this code to check that the references are 
2365      // correct (but it's slow)
2366      if (sourceForReference != null && uri.contains("ValueSet")) {
2367        if (!ResourceUtilities.hasURL(uri, sourceForReference)) {
2368          log.warn("Claimed source doesn't have url in it: "+sourceForReference.fhirType()+"/"+sourceForReference.getIdPart()+" -> "+uri);
2369        }
2370      }
2371    }
2372   
2373    List<String> pvlist = new ArrayList<>();
2374    if (sourceForReference != null && sourceForReference.getSourcePackage() != null) {
2375      populatePVList(pvlist, sourceForReference.getSourcePackage());
2376    }
2377    
2378    if (class_ == StructureDefinition.class) {
2379      uri = ProfileUtilities.sdNs(uri, null);
2380    }
2381    synchronized (lock) {
2382
2383      if (version == null) {
2384        if (uri.contains("|")) {
2385          version = uri.substring(uri.lastIndexOf("|")+1);
2386          uri = uri.substring(0, uri.lastIndexOf("|"));
2387        }
2388      } else {
2389        assert !uri.contains("|");
2390      }
2391      if (uri.contains("#")) {
2392        uri = uri.substring(0, uri.indexOf("#"));
2393      } 
2394      if (class_ == Resource.class || class_ == null) {
2395        if (structures.has(uri)) {
2396          return (T) structures.getByPackage(uri, version, pvlist);
2397        }        
2398        if (guides.has(uri)) {
2399          return (T) guides.getByPackage(uri, version, pvlist);
2400        } 
2401        if (capstmts.has(uri)) {
2402          return (T) capstmts.getByPackage(uri, version, pvlist);
2403        } 
2404        if (measures.has(uri)) {
2405          return (T) measures.getByPackage(uri, version, pvlist);
2406        } 
2407        if (libraries.has(uri)) {
2408          return (T) libraries.getByPackage(uri, version, pvlist);
2409        } 
2410        if (valueSets.has(uri)) {
2411          return (T) valueSets.getByPackage(uri, version, pvlist);
2412        } 
2413        if (codeSystems.has(uri)) {
2414          return (T) codeSystems.getByPackage(uri, version, pvlist);
2415        } 
2416        if (systems.has(uri)) {
2417          return (T) systems.getByPackage(uri, version, pvlist);
2418        } 
2419        if (operations.has(uri)) {
2420          return (T) operations.getByPackage(uri, version, pvlist);
2421        } 
2422        if (searchParameters.has(uri)) {
2423          return (T) searchParameters.getByPackage(uri, version, pvlist);
2424        } 
2425        if (plans.has(uri)) {
2426          return (T) plans.getByPackage(uri, version, pvlist);
2427        } 
2428        if (maps.has(uri)) {
2429          return (T) maps.getByPackage(uri, version, pvlist);
2430        } 
2431        if (transforms.has(uri)) {
2432          return (T) transforms.getByPackage(uri, version, pvlist);
2433        } 
2434        if (actors.has(uri)) {
2435          return (T) actors.getByPackage(uri, version, pvlist);
2436        } 
2437        if (requirements.has(uri)) {
2438          return (T) requirements.getByPackage(uri, version, pvlist);
2439        } 
2440        if (questionnaires.has(uri)) {
2441          return (T) questionnaires.getByPackage(uri, version, pvlist);
2442        } 
2443
2444        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
2445          for (ResourceProxy r : rt.values()) {
2446            if (uri.equals(r.getUrl())) {
2447              if (version == null || version == r.getResource().getMeta().getVersionId()) {
2448                return (T) r.getResource();
2449              }
2450            }
2451          }            
2452        }
2453        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
2454          return null;
2455        }
2456
2457        // it might be a special URL.
2458//        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
2459//          Resource res = null; // findTxValueSet(uri);
2460//          if (res != null) {
2461//            return (T) res;
2462//          }
2463//        }
2464        return null;      
2465      } else if (class_ == ImplementationGuide.class) {
2466        return (T) guides.getByPackage(uri, version, pvlist);
2467      } else if (class_ == CapabilityStatement.class) {
2468        return (T) capstmts.getByPackage(uri, version, pvlist);
2469      } else if (class_ == Measure.class) {
2470        return (T) measures.getByPackage(uri, version, pvlist);
2471      } else if (class_ == Library.class) {
2472        return (T) libraries.getByPackage(uri, version, pvlist);
2473      } else if (class_ == StructureDefinition.class) {
2474        return (T) structures.getByPackage(uri, version, pvlist);
2475      } else if (class_ == StructureMap.class) {
2476        return (T) transforms.getByPackage(uri, version, pvlist);
2477      } else if (class_ == NamingSystem.class) {
2478        return (T) systems.getByPackage(uri, version, pvlist);
2479      } else if (class_ == ValueSet.class) {
2480        return (T) valueSets.getByPackage(uri, version, pvlist);
2481      } else if (class_ == CodeSystem.class) {
2482        return (T) codeSystems.getByPackage(uri, version, pvlist);
2483      } else if (class_ == ConceptMap.class) {
2484        return (T) maps.getByPackage(uri, version, pvlist);
2485      } else if (class_ == ActorDefinition.class) {
2486        return (T) actors.getByPackage(uri, version, pvlist);
2487      } else if (class_ == Requirements.class) {
2488        return (T) requirements.getByPackage(uri, version, pvlist);
2489      } else if (class_ == PlanDefinition.class) {
2490        return (T) plans.getByPackage(uri, version, pvlist);
2491      } else if (class_ == OperationDefinition.class) {
2492        OperationDefinition od = operations.get(uri, version);
2493        return (T) od;
2494      } else if (class_ == Questionnaire.class) {
2495        return (T) questionnaires.getByPackage(uri, version, pvlist);
2496      } else if (class_ == SearchParameter.class) {
2497        SearchParameter res = searchParameters.getByPackage(uri, version, pvlist);
2498        return (T) res;
2499      }
2500      if (class_ == CodeSystem.class && codeSystems.has(uri)) { 
2501        return (T) codeSystems.getByPackage(uri, version, pvlist);
2502      }
2503      if (class_ == ValueSet.class && valueSets.has(uri)) {
2504        return (T) valueSets.getByPackage(uri, version, pvlist);
2505      } 
2506      
2507      if (class_ == Questionnaire.class) {
2508        return (T) questionnaires.getByPackage(uri, version, pvlist);
2509      } 
2510      if (supportedCodeSystems.containsKey(uri)) {
2511        return null;
2512      } 
2513      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
2514    }
2515  }
2516
2517  private void populatePVList(List<String> pvlist, PackageInformation sourcePackage) {
2518    pvlist.add(sourcePackage.getVID());
2519    List<String> toadd = new ArrayList<>();
2520    do {
2521      toadd.clear();
2522      for (String s : pvlist) {
2523        PackageInformation pi = packages.get(s);
2524        if (pi != null) {
2525          for (String v : pi.getDependencies()) {
2526            if (!pvlist.contains(v) && !toadd.contains(v)) {
2527              toadd.add(v);
2528            }
2529          }
2530        }        
2531      }
2532      pvlist.addAll(toadd);
2533    } while (toadd.size() > 0);
2534  }
2535
2536  public PackageInformation getPackageForUrl(String uri) {
2537    if (uri == null) {
2538      return null;
2539    }
2540    uri = ProfileUtilities.sdNs(uri, null);
2541
2542    synchronized (lock) {
2543
2544      String version = null;
2545      if (uri.contains("|")) {
2546        version = uri.substring(uri.lastIndexOf("|")+1);
2547        uri = uri.substring(0, uri.lastIndexOf("|"));
2548      }
2549      if (uri.contains("#")) {
2550        uri = uri.substring(0, uri.indexOf("#"));
2551      } 
2552      if (structures.has(uri)) {
2553        return structures.getPackageInfo(uri, version);
2554      }        
2555      if (guides.has(uri)) {
2556        return guides.getPackageInfo(uri, version);
2557      } 
2558      if (capstmts.has(uri)) {
2559        return capstmts.getPackageInfo(uri, version);
2560      } 
2561      if (measures.has(uri)) {
2562        return measures.getPackageInfo(uri, version);
2563      } 
2564      if (libraries.has(uri)) {
2565        return libraries.getPackageInfo(uri, version);
2566      } 
2567      if (valueSets.has(uri)) {
2568        return valueSets.getPackageInfo(uri, version);
2569      } 
2570      if (codeSystems.has(uri)) {
2571        return codeSystems.getPackageInfo(uri, version);
2572      } 
2573      if (operations.has(uri)) {
2574        return operations.getPackageInfo(uri, version);
2575      } 
2576      if (searchParameters.has(uri)) {
2577        return searchParameters.getPackageInfo(uri, version);
2578      } 
2579      if (plans.has(uri)) {
2580        return plans.getPackageInfo(uri, version);
2581      } 
2582      if (maps.has(uri)) {
2583        return maps.getPackageInfo(uri, version);
2584      } 
2585      if (transforms.has(uri)) {
2586        return transforms.getPackageInfo(uri, version);
2587      } 
2588      if (actors.has(uri)) {
2589        return actors.getPackageInfo(uri, version);
2590      } 
2591      if (requirements.has(uri)) {
2592        return requirements.getPackageInfo(uri, version);
2593      } 
2594      if (questionnaires.has(uri)) {
2595        return questionnaires.getPackageInfo(uri, version);
2596      }         
2597      return null;
2598    }
2599  }
2600  
2601  @SuppressWarnings("unchecked")
2602  public <T extends Resource> T fetchResourceWithExceptionByVersion(String cls, String uri, String version, CanonicalResource source) throws FHIRException {
2603    if (uri == null) {
2604      return null;
2605    }
2606   
2607    if ("StructureDefinition".equals(cls)) {
2608      uri = ProfileUtilities.sdNs(uri, null);
2609    }
2610    synchronized (lock) {
2611
2612      if (version == null) {
2613        if (uri.contains("|")) {
2614          version = uri.substring(uri.lastIndexOf("|")+1);
2615          uri = uri.substring(0, uri.lastIndexOf("|"));
2616        }
2617      } else {
2618        boolean b = !uri.contains("|");
2619        assert b;
2620      }
2621      if (uri.contains("#")) {
2622        uri = uri.substring(0, uri.indexOf("#"));
2623      } 
2624      if (cls == null || "Resource".equals(cls)) {
2625        if (structures.has(uri)) {
2626          return (T) structures.get(uri, version);
2627        } 
2628        if (guides.has(uri)) {
2629          return (T) guides.get(uri, version);
2630        } 
2631        if (capstmts.has(uri)) {
2632          return (T) capstmts.get(uri, version);
2633        } 
2634        if (measures.has(uri)) {
2635          return (T) measures.get(uri, version);
2636        } 
2637        if (libraries.has(uri)) {
2638          return (T) libraries.get(uri, version);
2639        } 
2640        if (valueSets.has(uri)) {
2641          return (T) valueSets.get(uri, version);
2642        } 
2643        if (codeSystems.has(uri)) {
2644          return (T) codeSystems.get(uri, version);
2645        } 
2646        if (operations.has(uri)) {
2647          return (T) operations.get(uri, version);
2648        } 
2649        if (searchParameters.has(uri)) {
2650          return (T) searchParameters.get(uri, version);
2651        } 
2652        if (plans.has(uri)) {
2653          return (T) plans.get(uri, version);
2654        } 
2655        if (maps.has(uri)) {
2656          return (T) maps.get(uri, version);
2657        } 
2658        if (transforms.has(uri)) {
2659          return (T) transforms.get(uri, version);
2660        } 
2661        if (actors.has(uri)) {
2662          return (T) actors.get(uri, version);
2663        } 
2664        if (requirements.has(uri)) {
2665          return (T) requirements.get(uri, version);
2666        } 
2667        if (questionnaires.has(uri)) {
2668          return (T) questionnaires.get(uri, version);
2669        } 
2670        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
2671          for (ResourceProxy r : rt.values()) {
2672            if (uri.equals(r.getUrl())) {
2673              return (T) r.getResource();
2674            }
2675          }            
2676        }
2677      } else if ("ImplementationGuide".equals(cls)) {
2678        return (T) guides.get(uri, version);
2679      } else if ("CapabilityStatement".equals(cls)) {
2680        return (T) capstmts.get(uri, version);
2681      } else if ("Measure".equals(cls)) {
2682        return (T) measures.get(uri, version);
2683      } else if ("Library".equals(cls)) {
2684        return (T) libraries.get(uri, version);
2685      } else if ("StructureDefinition".equals(cls)) {
2686        return (T) structures.get(uri, version);
2687      } else if ("StructureMap".equals(cls)) {
2688        return (T) transforms.get(uri, version);
2689      } else if ("Requirements".equals(cls)) {
2690        return (T) requirements.get(uri, version);
2691      } else if ("ActorDefinition".equals(cls)) {
2692        return (T) actors.get(uri, version);
2693      } else if ("ValueSet".equals(cls)) {
2694        return (T) valueSets.get(uri, version);
2695      } else if ("CodeSystem".equals(cls)) {
2696        return (T) codeSystems.get(uri, version);
2697      } else if ("NamingSystem".equals(cls)) {
2698        return (T) systems.get(uri, version);
2699      } else if ("ConceptMap".equals(cls)) {
2700        return (T) maps.get(uri, version);
2701      } else if ("PlanDefinition".equals(cls)) {
2702        return (T) plans.get(uri, version);
2703      } else if ("OperationDefinition".equals(cls)) {
2704        OperationDefinition od = operations.get(uri, version);
2705        return (T) od;
2706      } else if ("Questionnaire".equals(cls)) {
2707        return (T) questionnaires.get(uri, version);
2708      } else if ("SearchParameter".equals(cls)) {
2709        SearchParameter res = searchParameters.get(uri, version);
2710        return (T) res;
2711      }
2712      if ("CodeSystem".equals(cls) && codeSystems.has(uri)) {
2713        return (T) codeSystems.get(uri, version);
2714      } 
2715      if ("ValueSet".equals(cls) && valueSets.has(uri)) {
2716        return (T) valueSets.get(uri, version);
2717      } 
2718      
2719      if ("Questionnaire".equals(cls)) {
2720        return (T) questionnaires.get(uri, version);
2721      } 
2722      if (cls == null) {
2723        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
2724          return null;
2725        } 
2726
2727        // it might be a special URL.
2728        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
2729          Resource res = null; // findTxValueSet(uri);
2730          if (res != null) {
2731            return (T) res;
2732          } 
2733        }
2734        return null;      
2735      }    
2736      if (supportedCodeSystems.containsKey(uri)) {
2737        return null;
2738      } 
2739      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
2740    }
2741  }
2742
2743  public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_, FhirPublication fhirVersion) {
2744    return fetchResourcesByType(class_);
2745  }
2746  
2747  @SuppressWarnings("unchecked")
2748  public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_) {
2749
2750    List<T> res = new ArrayList<>();
2751
2752    if (class_ == Resource.class || class_ == ValueSet.class)  {
2753      if (!isAllowedToIterateTerminologyResources()) {
2754        // what's going on here?
2755        // it's not unusual to have >50k ValueSets in context. Iterating all of
2756        // them will cause every one of them to loaded through the lazy loading infrastructure. This
2757        // can consume upwards of 10GB of RAM.
2758        //
2759        // By default, the context won't let you do that. If you do want to do it, and take the performance hit, then
2760        // setAllowedToIterateTerminologyResources(true);
2761        throw new Error("This context is configured to not allow Iterating ValueSet resources due to performance concerns");
2762      }
2763    }
2764    synchronized (lock) {
2765
2766      if (class_ == Resource.class || class_ == DomainResource.class || class_ == CanonicalResource.class || class_ == null) {
2767        res.addAll((List<T>) structures.getList());
2768        res.addAll((List<T>) guides.getList());
2769        res.addAll((List<T>) capstmts.getList());
2770        res.addAll((List<T>) measures.getList());
2771        res.addAll((List<T>) libraries.getList());
2772        res.addAll((List<T>) valueSets.getList());
2773        res.addAll((List<T>) codeSystems.getList());
2774        res.addAll((List<T>) operations.getList());
2775        res.addAll((List<T>) searchParameters.getList());
2776        res.addAll((List<T>) plans.getList());
2777        res.addAll((List<T>) maps.getList());
2778        res.addAll((List<T>) transforms.getList());
2779        res.addAll((List<T>) questionnaires.getList());
2780        res.addAll((List<T>) systems.getList());
2781        res.addAll((List<T>) actors.getList());
2782        res.addAll((List<T>) requirements.getList());
2783      } else if (class_ == ImplementationGuide.class) {
2784        res.addAll((List<T>) guides.getList());
2785      } else if (class_ == CapabilityStatement.class) {
2786        res.addAll((List<T>) capstmts.getList());
2787      } else if (class_ == Measure.class) {
2788        res.addAll((List<T>) measures.getList());
2789      } else if (class_ == Library.class) {
2790        res.addAll((List<T>) libraries.getList());
2791      } else if (class_ == StructureDefinition.class) {
2792        res.addAll((List<T>) structures.getList());
2793      } else if (class_ == StructureMap.class) {
2794        res.addAll((List<T>) transforms.getList());
2795      } else if (class_ == ValueSet.class) {
2796        res.addAll((List<T>) valueSets.getList());
2797      } else if (class_ == CodeSystem.class) {
2798        res.addAll((List<T>) codeSystems.getList());
2799      } else if (class_ == NamingSystem.class) {
2800        res.addAll((List<T>) systems.getList());
2801      } else if (class_ == ActorDefinition.class) {
2802        res.addAll((List<T>) actors.getList());
2803      } else if (class_ == Requirements.class) {
2804        res.addAll((List<T>) requirements.getList());
2805      } else if (class_ == ConceptMap.class) {
2806        res.addAll((List<T>) maps.getList());
2807      } else if (class_ == PlanDefinition.class) {
2808        res.addAll((List<T>) plans.getList());
2809      } else if (class_ == OperationDefinition.class) {
2810        res.addAll((List<T>) operations.getList());
2811      } else if (class_ == Questionnaire.class) {
2812        res.addAll((List<T>) questionnaires.getList());
2813      } else if (class_ == SearchParameter.class) {
2814        res.addAll((List<T>) searchParameters.getList());
2815      }
2816    }
2817    return res;
2818  }
2819
2820  @SuppressWarnings("unchecked")
2821  public <T extends Resource> List<T> fetchResourceVersionsByTypeAndUrl(Class<T> class_, String url) {
2822
2823    List<T> res = new ArrayList<>();
2824
2825    synchronized (lock) {
2826
2827      if (class_ == Resource.class || class_ == DomainResource.class || class_ == CanonicalResource.class || class_ == null) {
2828        res.addAll((List<T>) structures.getVersionList(url));
2829        res.addAll((List<T>) guides.getVersionList(url));
2830        res.addAll((List<T>) capstmts.getVersionList(url));
2831        res.addAll((List<T>) measures.getVersionList(url));
2832        res.addAll((List<T>) libraries.getVersionList(url));
2833        res.addAll((List<T>) valueSets.getVersionList(url));
2834        res.addAll((List<T>) codeSystems.getVersionList(url));
2835        res.addAll((List<T>) operations.getVersionList(url));
2836        res.addAll((List<T>) searchParameters.getVersionList(url));
2837        res.addAll((List<T>) plans.getVersionList(url));
2838        res.addAll((List<T>) maps.getVersionList(url));
2839        res.addAll((List<T>) transforms.getVersionList(url));
2840        res.addAll((List<T>) questionnaires.getVersionList(url));
2841        res.addAll((List<T>) systems.getVersionList(url));
2842        res.addAll((List<T>) actors.getVersionList(url));
2843        res.addAll((List<T>) requirements.getVersionList(url));
2844      } else if (class_ == ImplementationGuide.class) {
2845        res.addAll((List<T>) guides.getVersionList(url));
2846      } else if (class_ == CapabilityStatement.class) {
2847        res.addAll((List<T>) capstmts.getVersionList(url));
2848      } else if (class_ == Measure.class) {
2849        res.addAll((List<T>) measures.getVersionList(url));
2850      } else if (class_ == Library.class) {
2851        res.addAll((List<T>) libraries.getVersionList(url));
2852      } else if (class_ == StructureDefinition.class) {
2853        res.addAll((List<T>) structures.getVersionList(url));
2854      } else if (class_ == StructureMap.class) {
2855        res.addAll((List<T>) transforms.getVersionList(url));
2856      } else if (class_ == ValueSet.class) {
2857        res.addAll((List<T>) valueSets.getVersionList(url));
2858      } else if (class_ == CodeSystem.class) {
2859        res.addAll((List<T>) codeSystems.getVersionList(url));
2860      } else if (class_ == NamingSystem.class) {
2861        res.addAll((List<T>) systems.getVersionList(url));
2862      } else if (class_ == ActorDefinition.class) {
2863        res.addAll((List<T>) actors.getVersionList(url));
2864      } else if (class_ == Requirements.class) {
2865        res.addAll((List<T>) requirements.getVersionList(url));
2866      } else if (class_ == ConceptMap.class) {
2867        res.addAll((List<T>) maps.getVersionList(url));
2868      } else if (class_ == PlanDefinition.class) {
2869        res.addAll((List<T>) plans.getVersionList(url));
2870      } else if (class_ == OperationDefinition.class) {
2871        res.addAll((List<T>) operations.getVersionList(url));
2872      } else if (class_ == Questionnaire.class) {
2873        res.addAll((List<T>) questionnaires.getVersionList(url));
2874      } else if (class_ == SearchParameter.class) {
2875        res.addAll((List<T>) searchParameters.getVersionList(url));
2876      }
2877    }
2878    return res;
2879  }
2880
2881  private Set<String> notCanonical = new HashSet<String>();
2882
2883  protected IWorkerContextManager.IPackageLoadingTracker packageTracker;
2884  private boolean forPublication;
2885  private boolean cachingAllowed = true;
2886  private static boolean nsFailHasFailed;
2887
2888  public Resource fetchResourceById(String type, String uri, FhirPublication fhirVersion) {
2889    return fetchResourceById(type, uri);
2890  }
2891  
2892  @Override
2893  public Resource fetchResourceById(String type, String uri) {
2894    synchronized (lock) {
2895      String[] parts = uri.split("\\/");
2896      if (!Utilities.noString(type) && parts.length == 1) {
2897        if (allResourcesById.containsKey(type)) {
2898          ResourceProxy res = allResourcesById.get(type).get(parts[0]);
2899          return res == null ? null : res.getResource();
2900        } else {
2901          return null;
2902        }
2903      }
2904      if (parts.length >= 2) {
2905        if (!Utilities.noString(type)) {
2906          if (!type.equals(parts[parts.length-2])) { 
2907            throw new Error(formatMessage(I18nConstants.RESOURCE_TYPE_MISMATCH_FOR___, type, uri));
2908          }
2909        }
2910        return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]).getResource();
2911      } else {
2912        throw new Error(formatMessage(I18nConstants.UNABLE_TO_PROCESS_REQUEST_FOR_RESOURCE_FOR___, type, uri));
2913      }
2914    }
2915  }
2916
2917  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version, Resource sourceForReference) {
2918    try {
2919      return fetchResourceWithException(class_, uri, version, sourceForReference);
2920    } catch (FHIRException e) {
2921      throw new Error(e);
2922    }    
2923  }
2924  
2925
2926  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
2927    try {
2928      return fetchResourceWithException(class_, uri, null, null);
2929    } catch (FHIRException e) {
2930      throw new Error(e);
2931    }
2932  }
2933
2934  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
2935    try {
2936      return fetchResourceWithExceptionByVersion(class_, uri, version, null);
2937    } catch (FHIRException e) {
2938      throw new Error(e);
2939    }
2940  }
2941  
2942  @Override
2943  public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
2944    try {
2945      return fetchResourceWithException(class_, uri) != null;
2946    } catch (Exception e) {
2947      return false;
2948    }
2949  }
2950
2951  public <T extends Resource> boolean hasResource(String cls, String uri) {
2952    try {
2953      return fetchResourceWithException(cls, uri) != null;
2954    } catch (Exception e) {
2955      return false;
2956    }
2957  }
2958
2959  public <T extends Resource> boolean hasResourceVersion(Class<T> class_, String uri, String version) {
2960    try {
2961      return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
2962    } catch (Exception e) {
2963      return false;
2964    }
2965  }
2966
2967  public <T extends Resource> boolean hasResourceVersion(String cls, String uri, String version) {
2968    try {
2969      return fetchResourceWithExceptionByVersion(cls, uri, version, null) != null;
2970    } catch (Exception e) {
2971      return false;
2972    }
2973  }
2974
2975  public <T extends Resource> boolean hasResource(Class<T> class_, String uri, String version, Resource sourceForReference) {
2976    try {
2977      return fetchResourceWithExceptionByVersion(class_, uri, version, sourceForReference) != null;
2978    } catch (Exception e) {
2979      return false;
2980    }
2981  }
2982
2983  public <T extends Resource> boolean hasResourceVersion(String cls, String uri, String version, FhirPublication fhirVersion) {
2984    try {
2985      return fetchResourceWithExceptionByVersion(cls, uri, version, null) != null;
2986    } catch (Exception e) {
2987      return false;
2988    }
2989  }
2990
2991  public <T extends Resource> boolean hasResource(Class<T> class_, String uri, Resource sourceOfReference) {
2992    try {
2993      return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
2994    } catch (Exception e) {
2995      return false;
2996    }
2997  }
2998
2999  public void reportStatus(JsonObject json) {
3000    synchronized (lock) {
3001      json.addProperty("codeystem-count", codeSystems.size());
3002      json.addProperty("valueset-count", valueSets.size());
3003      json.addProperty("conceptmap-count", maps.size());
3004      json.addProperty("transforms-count", transforms.size());
3005      json.addProperty("structures-count", structures.size());
3006      json.addProperty("guides-count", guides.size());
3007      json.addProperty("statements-count", capstmts.size());
3008      json.addProperty("measures-count", measures.size());
3009      json.addProperty("libraries-count", libraries.size());
3010    }
3011  }
3012
3013
3014  public void dropResource(Resource r) throws FHIRException {
3015    dropResource(r.fhirType(), r.getId());   
3016  }
3017
3018  public void dropResource(String fhirType, String id) {
3019    synchronized (lock) {
3020
3021      Map<String, ResourceProxy> map = allResourcesById.get(fhirType);
3022      if (map == null) {
3023        map = new HashMap<String, ResourceProxy>();
3024        allResourcesById.put(fhirType, map);
3025      }
3026      if (map.containsKey(id)) {
3027        map.remove(id); // this is a challenge because we might have more than one resource with this id (different versions)
3028      }
3029
3030      if (fhirType.equals("StructureDefinition")) {
3031        structures.drop(id);
3032        typeManager.reload();
3033      } else if (fhirType.equals("ImplementationGuide")) {
3034        guides.drop(id);
3035      } else if (fhirType.equals("CapabilityStatement")) {
3036        capstmts.drop(id);
3037      } else if (fhirType.equals("Measure")) {
3038        measures.drop(id);
3039      } else if (fhirType.equals("Library")) {
3040        libraries.drop(id);
3041      } else if (fhirType.equals("ValueSet")) {
3042        valueSets.drop(id);
3043      } else if (fhirType.equals("CodeSystem")) {
3044        codeSystems.drop(id);
3045      } else if (fhirType.equals("OperationDefinition")) {
3046        operations.drop(id);
3047      } else if (fhirType.equals("Questionnaire")) {
3048        questionnaires.drop(id);
3049      } else if (fhirType.equals("ConceptMap")) {
3050        maps.drop(id);
3051      } else if (fhirType.equals("StructureMap")) {
3052        transforms.drop(id);
3053      } else if (fhirType.equals("NamingSystem")) {
3054        systems.drop(id);
3055        systemUrlMap = null;
3056      } else if (fhirType.equals("ActorDefinition")) {
3057        actors.drop(id);
3058      } else if (fhirType.equals("Requirements")) {
3059        requirements.drop(id);
3060      }
3061    }
3062  }
3063
3064  private <T extends CanonicalResource> void dropMetadataResource(Map<String, T> map, String id) {
3065    T res = map.get(id);
3066    if (res != null) {
3067      map.remove(id);
3068      if (map.containsKey(res.getUrl())) {
3069        map.remove(res.getUrl());
3070      }
3071      if (res.getVersion() != null) {
3072        if (map.containsKey(res.getUrl()+"|"+res.getVersion())) {
3073          map.remove(res.getUrl()+"|"+res.getVersion());
3074        }
3075      }
3076    }
3077  }
3078
3079  
3080  public String listSupportedSystems() {
3081    synchronized (lock) {
3082      String sl = null;
3083      for (String s : supportedCodeSystems.keySet()) {
3084        SystemSupportInformation ss = supportedCodeSystems.get(s);
3085        if (ss.isSupported()) {
3086          sl = sl == null ? s : sl + "\r\n" + s;
3087        }
3088      }
3089      return sl;
3090    }
3091  }
3092
3093
3094  public int totalCount() {
3095    synchronized (lock) {
3096      return valueSets.size() +  maps.size() + structures.size() + transforms.size();
3097    }
3098  }
3099  
3100  public List<ConceptMap> listMaps() {
3101    List<ConceptMap> m = new ArrayList<ConceptMap>();
3102    synchronized (lock) {
3103      maps.listAll(m);
3104    }
3105    return m;
3106  }
3107
3108  public List<StructureDefinition> listStructures() {
3109    List<StructureDefinition> m = new ArrayList<StructureDefinition>();
3110    synchronized (lock) {
3111      structures.listAll(m);
3112    }
3113    return m;
3114  }
3115
3116  public List<ValueSet> listValueSets() {
3117    List<ValueSet> m = new ArrayList<ValueSet>();
3118    synchronized (lock) {
3119      valueSets.listAll(m);
3120    }
3121    return m;
3122  }
3123
3124  public List<CodeSystem> listCodeSystems() {
3125    List<CodeSystem> m = new ArrayList<CodeSystem>();
3126    synchronized (lock) {
3127      codeSystems.listAll(m);
3128    }
3129    return m;
3130  }
3131
3132  public StructureDefinition getStructure(String code) {
3133    synchronized (lock) {
3134      return structures.get(code);
3135    }
3136  }
3137
3138  private String getUri(NamingSystem ns) {
3139    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
3140      if (id.getType() == NamingSystemIdentifierType.URI) {
3141        return id.getValue();
3142      }
3143    }
3144    return null;
3145  }
3146
3147  private boolean hasOid(NamingSystem ns, String oid) {
3148    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
3149      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) {
3150        return true;
3151      }
3152    }
3153    return false;
3154  }
3155
3156  public void cacheVS(JsonObject json, Map<String, ValidationResult> t) {
3157    synchronized (lock) {
3158      validationCache.put(json.get("url").getAsString(), t);
3159    }
3160  }
3161
3162  public SearchParameter getSearchParameter(String code) {
3163    synchronized (lock) {
3164      return searchParameters.get(code);
3165    }
3166  }
3167
3168  @Override
3169  public org.hl7.fhir.r5.context.ILoggingService getLogger() {
3170    return logger;
3171  }
3172
3173
3174  public StructureDefinition fetchTypeDefinition(String typeName, FhirPublication fhirVersion) {
3175    return fetchTypeDefinition(typeName);
3176  }
3177
3178  @Override
3179  public StructureDefinition fetchTypeDefinition(String typeName) {
3180    if (Utilities.isAbsoluteUrl(typeName)) {
3181      StructureDefinition res = fetchResource(StructureDefinition.class, typeName);
3182      if (res != null) {
3183        return res;
3184      }
3185    } 
3186    StructureDefinition structureDefinition = typeManager.fetchTypeDefinition(typeName);
3187    generateSnapshot(structureDefinition, "5");
3188    return structureDefinition;
3189  }
3190
3191  void generateSnapshot(StructureDefinition structureDefinition, String breadcrumb) {
3192    if (structureDefinition != null && !structureDefinition.isGeneratedSnapshot()) {
3193      if (structureDefinition.isGeneratingSnapshot()) {
3194        throw new FHIRException("Attempt to fetch the profile "+ structureDefinition.getVersionedUrl()+" while generating the snapshot for it");
3195      }
3196      try {
3197
3198        // logger.logDebugMessage(LogCategory.GENERATE,"Generating snapshot for "+ structureDefinition.getVersionedUrl());
3199
3200       // structureDefinition.setGeneratingSnapshot(true);
3201        try {
3202          cutils.generateSnapshot(structureDefinition);
3203        } finally {
3204          //structureDefinition.setGeneratingSnapshot(false);
3205        }
3206      } catch (Exception e) {
3207        // not sure what to do in this case?
3208        log.error("Unable to generate snapshot in @" + breadcrumb + " for " + structureDefinition.getVersionedUrl()+": "+e.getMessage());
3209        logger.logDebugMessage(ILoggingService.LogCategory.GENERATE, ExceptionUtils.getStackTrace(e));
3210      }
3211    }
3212  }
3213
3214  @Override
3215  public List<StructureDefinition> fetchTypeDefinitions(String typeName) {
3216    return typeManager.getDefinitions(typeName);
3217  }
3218
3219  public boolean isPrimitiveType(String type) {
3220    return typeManager.isPrimitive(type);
3221  }
3222
3223  public boolean isDataType(String type) {
3224    return typeManager.isDataType(type);
3225  }
3226  
3227  public boolean isTlogging() {
3228    return tlogging;
3229  }
3230
3231  public void setTlogging(boolean tlogging) {
3232    this.tlogging = tlogging;
3233  }
3234
3235  public UcumService getUcumService() {
3236    return ucumService;
3237  }
3238
3239  public void setUcumService(UcumService ucumService) {
3240    this.ucumService = ucumService;
3241  }
3242
3243  public String getLinkForUrl(String corePath, String url) {
3244    if (url == null) {
3245      return null;
3246    }
3247    
3248    if (codeSystems.has(url)) {
3249      return codeSystems.get(url).getWebPath();
3250    }
3251
3252    if (valueSets.has(url)) {
3253      return valueSets.get(url).getWebPath();
3254    }
3255
3256    if (maps.has(url)) {
3257      return maps.get(url).getWebPath();
3258    }
3259    
3260    if (transforms.has(url)) {
3261      return transforms.get(url).getWebPath();
3262    }
3263    
3264    if (actors.has(url)) {
3265      return actors.get(url).getWebPath();
3266    }
3267    
3268    if (requirements.has(url)) {
3269      return requirements.get(url).getWebPath();
3270    }
3271    
3272    if (structures.has(url)) {
3273      return structures.get(url).getWebPath();
3274    }
3275    
3276    if (guides.has(url)) {
3277      return guides.get(url).getWebPath();
3278    }
3279    
3280    if (capstmts.has(url)) {
3281      return capstmts.get(url).getWebPath();
3282    }
3283    
3284    if (measures.has(url)) {
3285      return measures.get(url).getWebPath();
3286    }
3287
3288    if (libraries.has(url)) {
3289      return libraries.get(url).getWebPath();
3290    }
3291
3292    if (searchParameters.has(url)) {
3293      return searchParameters.get(url).getWebPath();
3294    }
3295        
3296    if (questionnaires.has(url)) {
3297      return questionnaires.get(url).getWebPath();
3298    }
3299
3300    if (operations.has(url)) {
3301      return operations.get(url).getWebPath();
3302    }
3303    
3304    if (plans.has(url)) {
3305      return plans.get(url).getWebPath();
3306    }
3307
3308    if (url.equals("http://loinc.org")) {
3309      return corePath+"loinc.html";
3310    }
3311    if (url.equals("http://unitsofmeasure.org")) {
3312      return corePath+"ucum.html";
3313    } 
3314    if (url.equals("http://snomed.info/sct")) {
3315      return corePath+"snomed.html";
3316    } 
3317    return null;
3318  }
3319
3320  public List<ImplementationGuide> allImplementationGuides() {
3321    List<ImplementationGuide> res = new ArrayList<>();
3322    guides.listAll(res);
3323    return res;
3324  }
3325
3326  @Override
3327  public Set<String> getBinaryKeysAsSet() { return binaries.keySet(); }
3328
3329  @Override
3330  public boolean hasBinaryKey(String binaryKey) {
3331    return binaries.containsKey(binaryKey);
3332  }
3333
3334  @Override
3335  public byte[] getBinaryForKey(String binaryKey) {
3336    IByteProvider bp = binaries.get(binaryKey);
3337    try {
3338      return bp == null ? null : bp.bytes();
3339    } catch (Exception e) {
3340      throw new FHIRException(e);
3341    }
3342  }
3343
3344  public void finishLoading(boolean genSnapshots) {
3345    if (!hasResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Base")) {
3346      cacheResource(ProfileUtilities.makeBaseDefinition(version));
3347    }
3348    new CoreVersionPinner(this).pinCoreVersions(listCodeSystems(), listValueSets(), listStructures());
3349    if(genSnapshots) {
3350      for (StructureDefinition sd : listStructures()) {
3351        try {
3352          if (sd.getSnapshot().isEmpty()) { 
3353            new ContextUtilities(this).generateSnapshot(sd);
3354            //          new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
3355          }
3356        } catch (Exception e) {
3357          log.error("Unable to generate snapshot @1 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
3358          logger.logDebugMessage(LogCategory.GENERATE, ExceptionUtils.getStackTrace(e));
3359        }
3360      }  
3361    }
3362    
3363    codeSystems.setVersion(version);
3364    valueSets.setVersion(version);
3365    maps.setVersion(version);
3366    transforms.setVersion(version);
3367    structures.setVersion(version);
3368    typeManager.reload();
3369    measures.setVersion(version);
3370    libraries.setVersion(version);
3371    guides.setVersion(version);
3372    capstmts.setVersion(version);
3373    searchParameters.setVersion(version);
3374    questionnaires.setVersion(version);
3375    operations.setVersion(version);
3376    plans.setVersion(version);
3377    systems.setVersion(version);
3378    actors.setVersion(version);
3379    requirements.setVersion(version);
3380  }
3381
3382  protected String tail(String url) {
3383    if (Utilities.noString(url)) {
3384      return "noname";
3385    }
3386    if (url.contains("/")) {
3387      return url.substring(url.lastIndexOf("/")+1);
3388    }
3389    return url;
3390  }
3391  
3392  public int getClientRetryCount() {
3393    return terminologyClientManager.getRetryCount();
3394  }
3395  
3396  public IWorkerContext setClientRetryCount(int value) {
3397    terminologyClientManager.setRetryCount(value);
3398    return this;
3399  }
3400
3401  public TerminologyClientManager getTxClientManager() {
3402    return terminologyClientManager;
3403  }
3404
3405  public String getCacheId() {
3406    return terminologyClientManager.getCacheId();
3407  }
3408
3409  public TimeTracker clock() {
3410    return clock;
3411  }
3412 
3413  public int countAllCaches() {
3414    return codeSystems.size() + valueSets.size() + maps.size() + transforms.size() + structures.size() + measures.size() + libraries.size() + 
3415        guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() + 
3416        systems.size()+ actors.size()+ requirements.size();
3417  }
3418
3419  public Set<String> getCodeSystemsUsed() {
3420    return codeSystemsUsed ;
3421  }
3422 
3423  public IWorkerContextManager.ICanonicalResourceLocator getLocator() {
3424    return locator;
3425  }
3426
3427  public void setLocator(IWorkerContextManager.ICanonicalResourceLocator locator) {
3428    this.locator = locator;
3429  }
3430
3431  public String getUserAgent() {
3432    return userAgent;
3433  }
3434
3435  protected void setUserAgent(String userAgent) {
3436    this.userAgent = userAgent;
3437    terminologyClientManager.setUserAgent(userAgent);
3438  }
3439
3440
3441  public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() {
3442    return packageTracker;
3443  }
3444  
3445  public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) {
3446    this.packageTracker = packageTracker;
3447    return this;
3448  }
3449  
3450
3451  @Override
3452  public PEBuilder getProfiledElementBuilder(PEElementPropertiesPolicy elementProps, boolean fixedProps) {
3453    // TODO Auto-generated method stub
3454    return new PEBuilder(this, elementProps, fixedProps);
3455  }
3456  
3457  public boolean isForPublication() {
3458    return forPublication;
3459  }
3460  
3461  public void setForPublication(boolean value) {
3462    forPublication = value;
3463  }
3464
3465  public boolean isCachingAllowed() {
3466    return cachingAllowed;
3467  }
3468
3469  public void setCachingAllowed(boolean cachingAllowed) {
3470    this.cachingAllowed = cachingAllowed;
3471  }
3472
3473  @Override
3474  public IOIDServices.OIDSummary urlsForOid(String oid, String resourceType) {
3475    IOIDServices.OIDSummary set = urlsForOid(oid, resourceType, true);
3476    if (set.getDefinitions().size() > 1) {
3477      set = urlsForOid(oid, resourceType, false);
3478    }
3479    return set;
3480  }
3481  
3482  public IOIDServices.OIDSummary urlsForOid(String oid, String resourceType, boolean retired) {
3483    IOIDServices.OIDSummary summary = new IOIDServices.OIDSummary();
3484    if (oid != null) {
3485      if (oidCacheManual.containsKey(oid)) {
3486        summary.addOIDs(oidCacheManual.get(oid));
3487      }
3488      for (OIDSource os : oidSources) {
3489        if (os.db == null) {
3490          os.db = connectToOidSource(os.folder);
3491        }
3492        if (os.db != null) {
3493          try {
3494            PreparedStatement psql = resourceType == null ?
3495                os.db.prepareStatement("Select TYPE, URL, VERSION, Status from OIDMap where OID = ?") :
3496                os.db.prepareStatement("Select TYPE, URL, VERSION, Status from OIDMap where TYPE = '"+resourceType+"' and OID = ?");
3497            psql.setString(1, oid);
3498            ResultSet rs = psql.executeQuery();
3499            while (rs.next()) {
3500              if (retired || !"retired".equals(rs.getString(4))) {
3501                String rt = rs.getString(1);
3502                String url = rs.getString(2);
3503                String version = rs.getString(3);
3504                String status = rs.getString(4);
3505                summary.addOID(new IOIDServices.OIDDefinition(rt, oid, url, version, os.pid, status));
3506              }
3507            }
3508          } catch (Exception e) {
3509            // nothing, there would alreagy have been an error
3510  //          e.printStackTrace();
3511          }
3512        }
3513      }      
3514  
3515      switch (oid) {
3516      case "2.16.840.1.113883.6.1" :
3517        summary.addOID(new IOIDServices.OIDDefinition("CodeSystem", "2.16.840.1.113883.6.1", "http://loinc.org", null, null, null));
3518        break;
3519      case "2.16.840.1.113883.6.8" :
3520        summary.addOID(new IOIDServices.OIDDefinition("CodeSystem", "2.16.840.1.113883.6.8", "http://unitsofmeasure.org", null, null, null));
3521        break;
3522      case "2.16.840.1.113883.6.96" :
3523        summary.addOID(new IOIDServices.OIDDefinition("CodeSystem", "2.16.840.1.113883.6.96", "http://snomed.info/sct", null, null, null));
3524        break;
3525      default:
3526      }
3527    }
3528    summary.sort();
3529    return summary;
3530  }
3531
3532  private Connection connectToOidSource(String folder) {
3533    try {
3534      File ff = ManagedFileAccess.file(folder);
3535      File of = ManagedFileAccess.file(Utilities.path(ff.getAbsolutePath(), ".oid-map-2.db"));
3536      if (!of.exists()) {
3537        OidIndexBuilder oidBuilder = new OidIndexBuilder(ff, of);
3538        oidBuilder.build();
3539      }
3540      return DriverManager.getConnection("jdbc:sqlite:"+of.getAbsolutePath());
3541    } catch (Exception e) {
3542      return null;
3543    }
3544  }
3545
3546
3547  public void unload() {
3548
3549    codeSystems.unload();
3550    valueSets.unload();
3551    maps.unload();
3552    transforms.unload();
3553    structures.unload();
3554    typeManager.unload();
3555    measures.unload();
3556    libraries.unload();
3557    guides.unload();
3558    capstmts.unload();
3559    searchParameters.unload();
3560    questionnaires.unload();
3561    operations.unload();
3562    plans.unload();
3563    actors.unload();
3564    requirements.unload();
3565    systems.unload();
3566
3567    binaries.clear();
3568    validationCache.clear();
3569    txCache.unload();
3570}
3571  
3572  private <T extends Resource> T doFindTxResource(Class<T> class_, String canonical) {
3573    // well, we haven't found it locally. We're going look it up
3574    if (class_ == ValueSet.class) {
3575      ValueSet ivs = new ImplicitValueSets(getExpansionParameters()).generateImplicitValueSet(canonical);
3576      if (ivs != null) {
3577        return (T) ivs;
3578      }
3579      SourcedValueSet svs = null;
3580      if (txCache.hasValueSet(canonical)) {
3581        svs = txCache.getValueSet(canonical);
3582      } else {
3583        svs = terminologyClientManager.findValueSetOnServer(canonical);
3584        txCache.cacheValueSet(canonical, svs);
3585      }
3586      if (svs != null) {
3587        String web = ExtensionUtilities.readStringExtension(svs.getVs(), ExtensionDefinitions.EXT_WEB_SOURCE_OLD, ExtensionDefinitions.EXT_WEB_SOURCE_NEW);
3588        if (web == null) {
3589          web = Utilities.pathURL(svs.getServer(), "ValueSet", svs.getVs().getIdBase());
3590        }
3591        svs.getVs().setWebPath(web);
3592        svs.getVs().setUserData(UserDataNames.render_external_link, svs.getServer()); // so we can render it differently
3593      }      
3594      if (svs == null) {
3595        return null;
3596      } else {
3597        cacheResource(svs.getVs());
3598        return (T) svs.getVs();
3599      }
3600    } else if (class_ == CodeSystem.class) {
3601      SourcedCodeSystem scs = null;
3602      if (txCache.hasCodeSystem(canonical)) {
3603        scs = txCache.getCodeSystem(canonical);
3604      } else {
3605        scs = terminologyClientManager.findCodeSystemOnServer(canonical);
3606        txCache.cacheCodeSystem(canonical, scs);
3607      }
3608      if (scs != null) {
3609        String web = ExtensionUtilities.readStringExtension(scs.getCs(), ExtensionDefinitions.EXT_WEB_SOURCE_OLD, ExtensionDefinitions.EXT_WEB_SOURCE_NEW);
3610        if (web == null) {
3611          web = Utilities.pathURL(scs.getServer(), "ValueSet", scs.getCs().getIdBase());
3612        }
3613        scs.getCs().setWebPath(web);
3614        scs.getCs().setUserData(UserDataNames.render_external_link, scs.getServer()); // so we can render it differently
3615      }      
3616      if (scs == null) {
3617        return null;
3618      } else {
3619        cacheResource(scs.getCs());
3620        return (T) scs.getCs();
3621      }
3622    } else {
3623      throw new Error("Not supported: doFindTxResource with type of "+class_.getName());
3624    }
3625  }
3626
3627  public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version, Resource sourceOfReference) {
3628    if (canonical == null) {
3629      return null;
3630    }
3631   T result = fetchResource(class_, canonical, version, sourceOfReference);
3632   if (result == null) {
3633     result = doFindTxResource(class_, canonical);
3634   }
3635   return result;
3636  }
3637
3638  public <T extends Resource> T findTxResource(Class<T> class_, String canonical) {
3639    if (canonical == null) {
3640      return null;
3641    }
3642    T result = fetchResource(class_, canonical);
3643    if (result == null) {
3644      result = doFindTxResource(class_, canonical);
3645    }
3646    return result;
3647  }
3648  
3649  public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version) {
3650    if (canonical == null) {
3651      return null;
3652    }
3653    T result = fetchResource(class_, canonical, version);
3654    if (result == null) {
3655      result = doFindTxResource(class_, canonical+"|"+version);
3656    }
3657    return result;
3658  }
3659
3660  @Override
3661  public <T extends Resource> List<T> fetchResourceVersions(Class<T> class_, String uri) {
3662    List<T> res = new ArrayList<>();
3663    if (uri != null && !uri.startsWith("#")) {
3664      if (class_ == StructureDefinition.class) {
3665        uri = ProfileUtilities.sdNs(uri, null);
3666      }
3667      if (uri.contains("|")) {
3668        throw new Error("at fetchResourceVersions, but a version is found in the uri - should not happen ('"+uri+"')");
3669      }
3670      if (uri.contains("#")) {
3671        uri = uri.substring(0, uri.indexOf("#"));
3672      } 
3673      synchronized (lock) {
3674        if (class_ == Resource.class || class_ == null) {
3675          List<ResourceProxy> list = allResourcesByUrl.get(uri);
3676          if (list != null) {
3677            for (ResourceProxy r : list) {
3678              if (uri.equals(r.getUrl())) {
3679                res.add((T) r.getResource());
3680              }
3681            }            
3682          }  
3683        }
3684        if (class_ == ImplementationGuide.class || class_ == Resource.class || class_ == null) {
3685          for (ImplementationGuide cr : guides.getForUrl(uri)) {
3686            res.add((T) cr);
3687          } 
3688        } else if (class_ == CapabilityStatement.class || class_ == Resource.class || class_ == null) {
3689          for (CapabilityStatement cr : capstmts.getForUrl(uri)) {
3690            res.add((T) cr);
3691          } 
3692        } else if (class_ == Measure.class || class_ == Resource.class || class_ == null) {
3693          for (Measure cr : measures.getForUrl(uri)) {
3694            res.add((T) cr);
3695          } 
3696        } else if (class_ == Library.class || class_ == Resource.class || class_ == null) {
3697          for (Library cr : libraries.getForUrl(uri)) {
3698            res.add((T) cr);
3699          } 
3700        } else if (class_ == StructureDefinition.class || class_ == Resource.class || class_ == null) {
3701          for (StructureDefinition cr : structures.getForUrl(uri)) {
3702            res.add((T) cr);
3703          } 
3704        } else if (class_ == StructureMap.class || class_ == Resource.class || class_ == null) {
3705          for (StructureMap cr : transforms.getForUrl(uri)) {
3706            res.add((T) cr);
3707          } 
3708        } else if (class_ == NamingSystem.class || class_ == Resource.class || class_ == null) {
3709          for (NamingSystem cr : systems.getForUrl(uri)) {
3710            res.add((T) cr);
3711          } 
3712        } else if (class_ == ValueSet.class || class_ == Resource.class || class_ == null) {
3713          for (ValueSet cr : valueSets.getForUrl(uri)) {
3714            res.add((T) cr);
3715          } 
3716        } else if (class_ == CodeSystem.class || class_ == Resource.class || class_ == null) {
3717          for (CodeSystem cr : codeSystems.getForUrl(uri)) {
3718            res.add((T) cr);
3719          } 
3720        } else if (class_ == ConceptMap.class || class_ == Resource.class || class_ == null) {
3721          for (ConceptMap cr : maps.getForUrl(uri)) {
3722            res.add((T) cr);
3723          } 
3724        } else if (class_ == ActorDefinition.class || class_ == Resource.class || class_ == null) {
3725          for (ActorDefinition cr : actors.getForUrl(uri)) {
3726            res.add((T) cr);
3727          } 
3728        } else if (class_ == Requirements.class || class_ == Resource.class || class_ == null) {
3729          for (Requirements cr : requirements.getForUrl(uri)) {
3730            res.add((T) cr);
3731          } 
3732        } else if (class_ == PlanDefinition.class || class_ == Resource.class || class_ == null) {
3733          for (PlanDefinition cr : plans.getForUrl(uri)) {
3734            res.add((T) cr);
3735          } 
3736        } else if (class_ == OperationDefinition.class || class_ == Resource.class || class_ == null) {
3737          for (OperationDefinition cr : operations.getForUrl(uri)) {
3738            res.add((T) cr);
3739          } 
3740        } else if (class_ == Questionnaire.class || class_ == Resource.class || class_ == null) {
3741          for (Questionnaire cr : questionnaires.getForUrl(uri)) {
3742            res.add((T) cr);
3743          } 
3744        } else if (class_ == SearchParameter.class || class_ == Resource.class || class_ == null) {
3745          for (SearchParameter cr : searchParameters.getForUrl(uri)) {
3746            res.add((T) cr);
3747          } 
3748        }
3749      }
3750    }
3751    return res;
3752  }
3753
3754  public void setLocale(Locale locale) {
3755    super.setLocale(locale);
3756    if (locale == null) {
3757      return;
3758    }
3759    final String languageTag = locale.toLanguageTag();
3760    if ("und".equals(languageTag)) {
3761      throw new FHIRException("The locale " + locale.toString() + " is not valid");
3762    }
3763
3764    if (expansionParameters.get() == null) {
3765      return;
3766    }
3767
3768    /*
3769    If displayLanguage is an existing parameter, we check to see if it was added automatically or explicitly set by
3770    the user
3771
3772      * If it was added automatically, we update it to the new locale
3773      * If it was set by the user, we do not update it.
3774
3775    In both cases, we are done.
3776    */
3777
3778    Parameters newExpansionParameters = copyExpansionParametersWithUserData();
3779
3780    int displayLanguageCount = 0;
3781    for (ParametersParameterComponent expParameter : newExpansionParameters.getParameter()) {
3782      if ("displayLanguage".equals(expParameter.getName())) {
3783        if (expParameter.hasUserData(UserDataNames.auto_added_parameter)) {
3784          expParameter.setValue(new CodeType(languageTag));
3785        }
3786        displayLanguageCount++;
3787      }
3788    }
3789    if (displayLanguageCount > 1) {
3790      throw new FHIRException("Multiple displayLanguage parameters found");
3791    }
3792    if (displayLanguageCount == 1) {
3793      this.expansionParameters.set(newExpansionParameters);
3794      return;
3795    }
3796
3797    // There is no displayLanguage parameter so we are free to add a "defaultDisplayLanguage" instead.
3798
3799    int defaultDisplayLanguageCount = 0;
3800    for (ParametersParameterComponent expansionParameter : newExpansionParameters.getParameter()) {
3801      if ("defaultDisplayLanguage".equals(expansionParameter.getName())) {
3802        expansionParameter.setValue(new CodeType(languageTag));
3803        expansionParameter.setUserData(UserDataNames.auto_added_parameter, true);
3804        defaultDisplayLanguageCount++;
3805      }
3806    }
3807    if (defaultDisplayLanguageCount > 1) {
3808      throw new FHIRException("Multiple defaultDisplayLanguage parameters found");
3809    }
3810    if (defaultDisplayLanguageCount == 0) {
3811      ParametersParameterComponent p = newExpansionParameters.addParameter();
3812      p.setName("defaultDisplayLanguage");
3813      p.setValue(new CodeType(languageTag));
3814      p.setUserData(UserDataNames.auto_added_parameter, true);
3815    }
3816    this.expansionParameters.set(newExpansionParameters);
3817  }
3818
3819  private Parameters copyExpansionParametersWithUserData() {
3820    Parameters newExpansionParameters = new Parameters();
3821    // Copy all existing parameters include userData (not usually included in copyies)
3822    if (this.expansionParameters.get() != null && this.expansionParameters.get().hasParameter()) {
3823      for (ParametersParameterComponent expParameter : this.expansionParameters.get().getParameter()) {
3824        ParametersParameterComponent copy = newExpansionParameters.addParameter();
3825        expParameter.copyValues(copy);
3826        copy.copyUserData(expParameter);
3827      }
3828    }
3829    return newExpansionParameters;
3830  }
3831
3832  @Override
3833  public OperationOutcome validateTxResource(ValidationOptions options, Resource resource) {
3834    if (resource instanceof ValueSet) {
3835      ValueSet vs = (ValueSet) resource;
3836      Set<String> systems = findRelevantSystems(vs);
3837      TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false);
3838      if (tc == null) {
3839        throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
3840      }
3841      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
3842        codeSystemsUsed.add(inc.getSystem());
3843      }
3844      for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
3845        codeSystemsUsed.add(inc.getSystem());
3846      }
3847
3848      txLog("$validate ValueSet on "+tc.getAddress());
3849      if (txLog != null) {
3850        txLog.clearLastId();
3851      }
3852      return tc.getClient().validateResource(vs);
3853    }
3854    return null;
3855  }
3856
3857
3858  public List<String> getSuppressedMappings() {
3859    return suppressedMappings;
3860  }
3861
3862  public void setSuppressedMappings(List<String> suppressedMappings) {
3863    this.suppressedMappings = suppressedMappings;
3864    this.cutils.setSuppressedMappings(suppressedMappings);
3865  }
3866
3867  public ContextUtilities getCutils() {
3868    return cutils;
3869  }
3870
3871
3872  public String txCacheReport() {
3873    return txCache.getReport();
3874  }
3875
3876
3877  public static boolean isAllowedToIterateTerminologyResources() {
3878    return allowedToIterateTerminologyResources;
3879  }
3880
3881  public static void setAllowedToIterateTerminologyResources(boolean allowedToIterateTerminologyResources) {
3882    BaseWorkerContext.allowedToIterateTerminologyResources = allowedToIterateTerminologyResources;
3883  }
3884
3885  public IOIDServices oidServices() {
3886    return this;
3887  }
3888
3889  public IWorkerContextManager getManager() {
3890    return this;
3891  }
3892}