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