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