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