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.util.ArrayList;
038import java.util.Collections;
039import java.util.Comparator;
040import java.util.Date;
041import java.util.HashMap;
042import java.util.HashSet;
043import java.util.List;
044import java.util.Locale;
045import java.util.Map;
046import java.util.Set;
047
048import lombok.Getter;
049import org.apache.commons.lang3.StringUtils;
050import org.fhir.ucum.UcumService;
051import org.hl7.fhir.exceptions.DefinitionException;
052import org.hl7.fhir.exceptions.FHIRException;
053import org.hl7.fhir.exceptions.NoTerminologyServiceException;
054import org.hl7.fhir.exceptions.TerminologyServiceException;
055import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
056import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy;
057import org.hl7.fhir.r5.context.IWorkerContext.ILoggingService.LogCategory;
058import org.hl7.fhir.r5.context.TerminologyCache.CacheToken;
059import org.hl7.fhir.r5.model.ActorDefinition;
060import org.hl7.fhir.r5.model.BooleanType;
061import org.hl7.fhir.r5.model.Bundle;
062import org.hl7.fhir.r5.model.CanonicalResource;
063import org.hl7.fhir.r5.model.CanonicalType;
064import org.hl7.fhir.r5.model.CapabilityStatement;
065import org.hl7.fhir.r5.model.CodeSystem;
066import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
067import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
068import org.hl7.fhir.r5.model.CodeableConcept;
069import org.hl7.fhir.r5.model.Coding;
070import org.hl7.fhir.r5.model.ConceptMap;
071import org.hl7.fhir.r5.model.Constants;
072import org.hl7.fhir.r5.model.DomainResource;
073import org.hl7.fhir.r5.model.ElementDefinition;
074import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
075import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
076import org.hl7.fhir.r5.model.IdType;
077import org.hl7.fhir.r5.model.ImplementationGuide;
078import org.hl7.fhir.r5.model.Library;
079import org.hl7.fhir.r5.model.Measure;
080import org.hl7.fhir.r5.model.NamingSystem;
081import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType;
082import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent;
083import org.hl7.fhir.r5.model.OperationDefinition;
084import org.hl7.fhir.r5.model.OperationOutcome;
085import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
086import org.hl7.fhir.r5.model.PackageInformation;
087import org.hl7.fhir.r5.model.Parameters;
088import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
089import org.hl7.fhir.r5.model.PlanDefinition;
090import org.hl7.fhir.r5.model.PrimitiveType;
091import org.hl7.fhir.r5.model.Questionnaire;
092import org.hl7.fhir.r5.model.Requirements;
093import org.hl7.fhir.r5.model.Resource;
094import org.hl7.fhir.r5.model.SearchParameter;
095import org.hl7.fhir.r5.model.StringType;
096import org.hl7.fhir.r5.model.StructureDefinition;
097import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
098import org.hl7.fhir.r5.model.StructureMap;
099import org.hl7.fhir.r5.model.TerminologyCapabilities;
100import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent;
101import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesExpansionParameterComponent;
102import org.hl7.fhir.r5.model.UriType;
103import org.hl7.fhir.r5.model.ValueSet;
104import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
105import org.hl7.fhir.r5.model.Bundle.BundleType;
106import org.hl7.fhir.r5.model.Bundle.HTTPVerb;
107import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
108import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
109import org.hl7.fhir.r5.profilemodel.PEDefinition;
110import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
111import org.hl7.fhir.r5.profilemodel.PEBuilder;
112import org.hl7.fhir.r5.renderers.OperationOutcomeRenderer;
113import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
114import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander;
115import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
116import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
117import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
118import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
119import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
120import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
121import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
122import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
123import org.hl7.fhir.r5.terminologies.client.TerminologyClientContext;
124import org.hl7.fhir.r5.utils.PackageHackerR5;
125import org.hl7.fhir.r5.utils.ResourceUtilities;
126import org.hl7.fhir.r5.utils.ToolingExtensions;
127import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
128import org.hl7.fhir.utilities.TimeTracker;
129import org.hl7.fhir.utilities.ToolingClientLogger;
130import org.hl7.fhir.utilities.TranslationServices;
131import org.hl7.fhir.utilities.Utilities;
132import org.hl7.fhir.utilities.VersionUtilities;
133import org.hl7.fhir.utilities.i18n.I18nBase;
134import org.hl7.fhir.utilities.i18n.I18nConstants;
135import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
136import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
137import org.hl7.fhir.utilities.validation.ValidationOptions;
138import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode;
139
140import com.google.gson.JsonObject;
141
142import javax.annotation.Nonnull;
143
144public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext{
145
146  private static final boolean QA_CHECK_REFERENCE_SOURCE = false; // see comments below
147
148  public class ResourceProxy {
149    private Resource resource;
150    private CanonicalResourceProxy proxy;
151
152    public ResourceProxy(Resource resource) {
153      super();
154      this.resource = resource;
155    }
156    public ResourceProxy(CanonicalResourceProxy proxy) {
157      super();
158      this.proxy = proxy;
159    }
160    
161    public Resource getResource() {
162      return resource != null ? resource : proxy.getResource();
163    }
164      
165    public CanonicalResourceProxy getProxy() {
166      return proxy;
167    }
168    
169    public String getUrl() {
170      if (resource == null) {
171        return proxy.getUrl();
172      } else if (resource instanceof CanonicalResource) {
173        return ((CanonicalResource) resource).getUrl(); 
174      } else {
175        return null;
176      }
177    }
178    
179  }
180
181  public class MetadataResourceVersionComparator<T extends CanonicalResource> implements Comparator<T> {
182
183    final private List<T> list;
184
185    public MetadataResourceVersionComparator(List<T> list) {
186      this.list = list;
187    }
188
189    @Override
190    public int compare(T arg1, T arg2) {
191      String v1 = arg1.getVersion();
192      String v2 = arg2.getVersion();
193      if (v1 == null && v2 == null) {
194        return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order
195      } else if (v1 == null) {
196        return -1;
197      } else if (v2 == null) {
198        return 1;
199      } else {
200        String mm1 = VersionUtilities.getMajMin(v1);
201        String mm2 = VersionUtilities.getMajMin(v2);
202        if (mm1 == null || mm2 == null) {
203          return v1.compareTo(v2);
204        } else {
205          return mm1.compareTo(mm2);
206        }
207      }
208    }
209  }
210
211  private Object lock = new Object(); // used as a lock for the data that follows
212  protected String version; // although the internal resources are all R5, the version of FHIR they describe may not be 
213
214  protected TerminologyClientContext tcc = new TerminologyClientContext();
215  private boolean minimalMemory = false;
216
217  private Map<String, Map<String, ResourceProxy>> allResourcesById = new HashMap<String, Map<String, ResourceProxy>>();
218  // all maps are to the full URI
219  private CanonicalResourceManager<CodeSystem> codeSystems = new CanonicalResourceManager<CodeSystem>(false, minimalMemory);
220  private final Set<String> supportedCodeSystems = new HashSet<String>();
221  private final Set<String> unsupportedCodeSystems = new HashSet<String>(); // know that the terminology server doesn't support them
222  private CanonicalResourceManager<ValueSet> valueSets = new CanonicalResourceManager<ValueSet>(false, minimalMemory);
223  private CanonicalResourceManager<ConceptMap> maps = new CanonicalResourceManager<ConceptMap>(false, minimalMemory);
224  protected CanonicalResourceManager<StructureMap> transforms = new CanonicalResourceManager<StructureMap>(false, minimalMemory);
225  private CanonicalResourceManager<StructureDefinition> structures = new CanonicalResourceManager<StructureDefinition>(false, minimalMemory);
226  private final CanonicalResourceManager<Measure> measures = new CanonicalResourceManager<Measure>(false, minimalMemory);
227  private final CanonicalResourceManager<Library> libraries = new CanonicalResourceManager<Library>(false, minimalMemory);
228  private CanonicalResourceManager<ImplementationGuide> guides = new CanonicalResourceManager<ImplementationGuide>(false, minimalMemory);
229  private final CanonicalResourceManager<CapabilityStatement> capstmts = new CanonicalResourceManager<CapabilityStatement>(false, minimalMemory);
230  private final CanonicalResourceManager<SearchParameter> searchParameters = new CanonicalResourceManager<SearchParameter>(false, minimalMemory);
231  private final CanonicalResourceManager<Questionnaire> questionnaires = new CanonicalResourceManager<Questionnaire>(false, minimalMemory);
232  private final CanonicalResourceManager<OperationDefinition> operations = new CanonicalResourceManager<OperationDefinition>(false, minimalMemory);
233  private final CanonicalResourceManager<PlanDefinition> plans = new CanonicalResourceManager<PlanDefinition>(false, minimalMemory);
234  private final CanonicalResourceManager<ActorDefinition> actors = new CanonicalResourceManager<ActorDefinition>(false, minimalMemory);
235  private final CanonicalResourceManager<Requirements> requirements = new CanonicalResourceManager<Requirements>(false, minimalMemory);
236  private final CanonicalResourceManager<NamingSystem> systems = new CanonicalResourceManager<NamingSystem>(false, minimalMemory);
237  private Map<String, NamingSystem> systemUrlMap;
238
239  
240  private UcumService ucumService;
241  protected Map<String, byte[]> binaries = new HashMap<String, byte[]>();
242  protected Map<String, String> oidCache = new HashMap<>();
243
244  protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String,ValidationResult>>();
245  protected String name;
246  private boolean allowLoadingDuplicates;
247
248  private final Set<String> codeSystemsUsed = new HashSet<>();
249  protected ToolingClientLogger txLog;
250  private boolean canRunWithoutTerminology;
251  protected boolean noTerminologyServer;
252  private int expandCodesLimit = 1000;
253  protected ILoggingService logger = new SystemOutLoggingService();
254  protected Parameters expParameters;
255  private TranslationServices translator = new NullTranslator();
256  private Map<String, PackageInformation> packages = new HashMap<>();
257
258  @Getter
259  protected TerminologyCache txCache;
260  protected TimeTracker clock;
261  private boolean tlogging = true;
262  private IWorkerContextManager.ICanonicalResourceLocator locator;
263  protected String userAgent;
264
265  protected BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException {
266    setValidationMessageLanguage(getLocale());
267    clock = new TimeTracker();
268  }
269
270  protected BaseWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException {
271    setValidationMessageLanguage(locale);
272    clock = new TimeTracker();
273  }
274
275  protected BaseWorkerContext(CanonicalResourceManager<CodeSystem> codeSystems, CanonicalResourceManager<ValueSet> valueSets, CanonicalResourceManager<ConceptMap> maps, CanonicalResourceManager<StructureDefinition> profiles,
276      CanonicalResourceManager<ImplementationGuide> guides) throws FileNotFoundException, IOException, FHIRException {
277    this();
278    this.codeSystems = codeSystems;
279    this.valueSets = valueSets;
280    this.maps = maps;
281    this.structures = profiles;
282    this.guides = guides;
283    clock = new TimeTracker();
284  }
285
286  protected void copy(BaseWorkerContext other) {
287    synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet 
288      allResourcesById.putAll(other.allResourcesById);
289      translator = other.translator;
290      codeSystems.copy(other.codeSystems);
291      valueSets.copy(other.valueSets);
292      maps.copy(other.maps);
293      transforms.copy(other.transforms);
294      structures.copy(other.structures);
295      searchParameters.copy(other.searchParameters);
296      plans.copy(other.plans);
297      questionnaires.copy(other.questionnaires);
298      operations.copy(other.operations);
299      systems.copy(other.systems);
300      systemUrlMap = null;
301      guides.copy(other.guides);
302      capstmts.copy(other.capstmts);
303      measures.copy(other.measures);
304      libraries.copy(libraries);
305
306      allowLoadingDuplicates = other.allowLoadingDuplicates;
307      name = other.name;
308      txLog = other.txLog;
309      canRunWithoutTerminology = other.canRunWithoutTerminology;
310      noTerminologyServer = other.noTerminologyServer;
311      if (other.txCache != null)
312        txCache = other.txCache; // no copy. for now?
313      expandCodesLimit = other.expandCodesLimit;
314      logger = other.logger;
315      expParameters = other.expParameters;
316      version = other.version;
317      supportedCodeSystems.addAll(other.supportedCodeSystems);
318      unsupportedCodeSystems.addAll(other.unsupportedCodeSystems);
319      codeSystemsUsed.addAll(other.codeSystemsUsed);
320      ucumService = other.ucumService;
321      binaries.putAll(other.binaries);
322      oidCache.putAll(other.oidCache);
323      validationCache.putAll(other.validationCache);
324      tlogging = other.tlogging;
325      locator = other.locator;
326      userAgent = other.userAgent;
327      tcc.copy(other.tcc);
328      cachingAllowed = other.cachingAllowed;
329    }
330  }
331  
332  
333  public void cacheResource(Resource r) throws FHIRException {
334    cacheResourceFromPackage(r, null);  
335  }
336  
337
338  public void registerResourceFromPackage(CanonicalResourceProxy r, PackageInformation packageInfo) throws FHIRException {    
339    PackageHackerR5.fixLoadedResource(r, packageInfo);
340
341    synchronized (lock) {
342      if (packageInfo != null) {
343        packages.put(packageInfo.getVID(), packageInfo);
344      }
345      if (r.getId() != null) {
346        Map<String, ResourceProxy> map = allResourcesById.get(r.getType());
347        if (map == null) {
348          map = new HashMap<String, ResourceProxy>();
349          allResourcesById.put(r.getType(), map);
350        }
351        if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) {
352          map.put(r.getId(), new ResourceProxy(r));
353        }
354      }
355
356      String url = r.getUrl();
357      if (!allowLoadingDuplicates && hasResourceVersion(r.getType(), url, r.getVersion()) && !packageInfo.isHTO()) {
358        // spcial workaround for known problems with existing packages
359        if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) {
360          return;
361        }
362        CanonicalResource ex = fetchResourceWithException(r.getType(), url);
363        throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url, r.getVersion(), ex.getVersion(),
364            ex.fhirType()));
365      }
366      switch(r.getType()) {
367      case "StructureDefinition":
368        if ("1.4.0".equals(version)) {
369          StructureDefinition sd = (StructureDefinition) r.getResource();
370          fixOldSD(sd);
371        }
372        structures.register(r, packageInfo);
373        break;
374      case "ValueSet":
375        valueSets.register(r, packageInfo);
376        break;
377      case "CodeSystem":
378        codeSystems.register(r, packageInfo);
379        break;
380      case "ImplementationGuide":
381        guides.register(r, packageInfo);
382        break;
383      case "CapabilityStatement":
384        capstmts.register(r, packageInfo);
385        break;
386      case "Measure":
387        measures.register(r, packageInfo);
388        break;
389      case "Library":
390        libraries.register(r, packageInfo);
391        break;
392      case "SearchParameter":
393        searchParameters.register(r, packageInfo);
394        break;
395      case "PlanDefinition":
396        plans.register(r, packageInfo);
397        break;
398      case "OperationDefinition":
399        operations.register(r, packageInfo);
400        break;
401      case "Questionnaire":
402        questionnaires.register(r, packageInfo);
403        break;
404      case "ConceptMap":
405        maps.register(r, packageInfo);
406        break;
407      case "StructureMap":
408        transforms.register(r, packageInfo);
409        break;
410      case "NamingSystem":
411        systems.register(r, packageInfo);
412        break;
413      case "Requirements":
414        requirements.register(r, packageInfo);
415        break;
416      case "ActorDefinition":
417        actors.register(r, packageInfo);
418        break;
419      }
420    }
421  }
422
423  public void cacheResourceFromPackage(Resource r, PackageInformation packageInfo) throws FHIRException {
424 
425    synchronized (lock) {   
426      if (packageInfo != null) {
427        packages.put(packageInfo.getVID(), packageInfo);
428      }
429
430      if (r.getId() != null) {
431        Map<String, ResourceProxy> map = allResourcesById.get(r.fhirType());
432        if (map == null) {
433          map = new HashMap<String, ResourceProxy>();
434          allResourcesById.put(r.fhirType(), map);
435        }
436        if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) {
437          map.put(r.getId(), new ResourceProxy(r));
438        } else {
439          logger.logDebugMessage(LogCategory.PROGRESS,"Ignore "+r.fhirType()+"/"+r.getId()+" from package "+packageInfo.toString());
440        }
441      }
442
443      if (r instanceof CodeSystem || r instanceof NamingSystem) {
444        oidCache.clear();
445      }
446
447      if (r instanceof CanonicalResource) {
448        CanonicalResource m = (CanonicalResource) r;
449        String url = m.getUrl();
450        if (!allowLoadingDuplicates && hasResource(r.getClass(), url)) {
451          // special workaround for known problems with existing packages
452          if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) {
453            return;
454          }
455          CanonicalResource ex = (CanonicalResource) fetchResourceWithException(r.getClass(), url);
456          throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url, ((CanonicalResource) r).getVersion(), ex.getVersion(),
457              ex.fhirType()));
458        }
459        if (r instanceof StructureDefinition) {
460          StructureDefinition sd = (StructureDefinition) m;
461          if ("1.4.0".equals(version)) {
462            fixOldSD(sd);
463          }
464          structures.see(sd, packageInfo);
465        } else if (r instanceof ValueSet) {
466          valueSets.see((ValueSet) m, packageInfo);
467        } else if (r instanceof CodeSystem) {
468          CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) r);
469          codeSystems.see((CodeSystem) m, packageInfo);
470        } else if (r instanceof ImplementationGuide) {
471          guides.see((ImplementationGuide) m, packageInfo);
472        } else if (r instanceof CapabilityStatement) {
473          capstmts.see((CapabilityStatement) m, packageInfo);
474        } else if (r instanceof Measure) {
475          measures.see((Measure) m, packageInfo);
476        } else if (r instanceof Library) {
477          libraries.see((Library) m, packageInfo);        
478        } else if (r instanceof SearchParameter) {
479          searchParameters.see((SearchParameter) m, packageInfo);
480        } else if (r instanceof PlanDefinition) {
481          plans.see((PlanDefinition) m, packageInfo);
482        } else if (r instanceof OperationDefinition) {
483          operations.see((OperationDefinition) m, packageInfo);
484        } else if (r instanceof Questionnaire) {
485          questionnaires.see((Questionnaire) m, packageInfo);
486        } else if (r instanceof ConceptMap) {
487          maps.see((ConceptMap) m, packageInfo);
488        } else if (r instanceof StructureMap) {
489          transforms.see((StructureMap) m, packageInfo);
490        } else if (r instanceof NamingSystem) {
491          systems.see((NamingSystem) m, packageInfo);
492          systemUrlMap = null;
493        } else if (r instanceof Requirements) {
494          requirements.see((Requirements) m, packageInfo);
495        } else if (r instanceof ActorDefinition) {
496          actors.see((ActorDefinition) m, packageInfo);
497          systemUrlMap = null;
498        }
499      }
500    }
501  }
502
503  public Map<String, NamingSystem> getNSUrlMap() {
504    if (systemUrlMap == null) {
505      systemUrlMap = new HashMap<>();
506      List<NamingSystem> nsl = systems.getList();
507      for (NamingSystem ns : nsl) {
508        for (NamingSystemUniqueIdComponent uid : ns.getUniqueId()) {
509          if (uid.getType() == NamingSystemIdentifierType.URI && uid.hasValue()) {
510            systemUrlMap.put(uid.getValue(), ns) ;
511          }
512        }        
513      }
514    }
515    return systemUrlMap;
516  }
517
518  
519  public void fixOldSD(StructureDefinition sd) {
520    if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension") && sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
521      sd.setSnapshot(null);
522    }
523    for (ElementDefinition ed : sd.getDifferential().getElement()) {
524      if (ed.getPath().equals("Extension.url") || ed.getPath().endsWith(".extension.url") ) {
525        ed.setMin(1);
526        if (ed.hasBase()) {
527          ed.getBase().setMin(1);
528        }
529      }
530      if ("extension".equals(ed.getSliceName())) {
531        ed.setSliceName(null);
532      }
533    }
534  }
535
536  /*
537   *  Compare business versions, returning "true" if the candidate newer version is in fact newer than the oldVersion
538   *  Comparison will work for strictly numeric versions as well as multi-level versions separated by ., -, _, : or space
539   *  Failing that, it will do unicode-based character ordering.
540   *  E.g. 1.5.3 < 1.14.3
541   *       2017-3-10 < 2017-12-7
542   *       A3 < T2
543   */
544  private boolean laterVersion(String newVersion, String oldVersion) {
545    // Compare business versions, retur
546    newVersion = newVersion.trim();
547    oldVersion = oldVersion.trim();
548    if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion)) {
549      return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion);
550    } else if (hasDelimiter(newVersion, oldVersion, ".")) {
551      return laterDelimitedVersion(newVersion, oldVersion, "\\.");
552    } else if (hasDelimiter(newVersion, oldVersion, "-")) {
553      return laterDelimitedVersion(newVersion, oldVersion, "\\-");
554    } else if (hasDelimiter(newVersion, oldVersion, "_")) {
555      return laterDelimitedVersion(newVersion, oldVersion, "\\_");
556    } else if (hasDelimiter(newVersion, oldVersion, ":")) {
557      return laterDelimitedVersion(newVersion, oldVersion, "\\:");
558    } else if (hasDelimiter(newVersion, oldVersion, " ")) {
559      return laterDelimitedVersion(newVersion, oldVersion, "\\ ");
560    } else {
561      return newVersion.compareTo(oldVersion) > 0;
562    }
563  }
564  
565  /*
566   * Returns true if both strings include the delimiter and have the same number of occurrences of it
567   */
568  private boolean hasDelimiter(String s1, String s2, String delimiter) {
569    return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length;
570  }
571
572  private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) {
573    String[] newParts = newVersion.split(delimiter);
574    String[] oldParts = oldVersion.split(delimiter);
575    for (int i = 0; i < newParts.length; i++) {
576      if (!newParts[i].equals(oldParts[i])) {
577        return laterVersion(newParts[i], oldParts[i]);
578      }
579    }
580    // This should never happen
581    throw new Error(formatMessage(I18nConstants.DELIMITED_VERSIONS_HAVE_EXACT_MATCH_FOR_DELIMITER____VS_, delimiter, newParts, oldParts));
582  }
583  
584  protected <T extends CanonicalResource> void seeMetadataResource(T r, Map<String, T> map, List<T> list, boolean addId) throws FHIRException {
585//    if (addId)
586    //      map.put(r.getId(), r); // todo: why?
587    list.add(r);
588    if (r.hasUrl()) {
589      // first, this is the correct reosurce for this version (if it has a version)
590      if (r.hasVersion()) {
591        map.put(r.getUrl()+"|"+r.getVersion(), r);
592      }
593      // if we haven't get anything for this url, it's the correct version
594      if (!map.containsKey(r.getUrl())) {
595        map.put(r.getUrl(), r);
596      } else {
597        List<T> rl = new ArrayList<T>();
598        for (T t : list) {
599          if (t.getUrl().equals(r.getUrl()) && !rl.contains(t)) {
600            rl.add(t);
601          }
602        }
603        Collections.sort(rl, new MetadataResourceVersionComparator<T>(list));
604        map.put(r.getUrl(), rl.get(rl.size()-1));
605        T latest = null;
606        for (T t : rl) {
607          if (VersionUtilities.versionsCompatible(t.getVersion(), r.getVersion())) {
608            latest = t;
609          }
610        }
611        if (latest != null) { // might be null if it's not using semver
612          map.put(r.getUrl()+"|"+VersionUtilities.getMajMin(latest.getVersion()), rl.get(rl.size()-1));
613        }
614      }
615    }
616  }  
617
618  @Override
619  public CodeSystem fetchCodeSystem(String system) {
620    if (system == null) {
621      return null;
622    }
623    if (system.contains("|")) {
624      String s = system.substring(0, system.indexOf("|"));
625      String v = system.substring(system.indexOf("|")+1);
626      return fetchCodeSystem(s, v);
627    }
628    CodeSystem cs;
629    synchronized (lock) {
630      cs = codeSystems.get(system);
631    }
632    if (cs == null && locator != null) {
633      locator.findResource(this, system);
634      synchronized (lock) {
635        cs = codeSystems.get(system);
636      }
637    }
638    return cs;
639  } 
640
641  public CodeSystem fetchCodeSystem(String system, String version) {
642    if (version == null) {
643      return fetchCodeSystem(system);
644    }
645    CodeSystem cs;
646    synchronized (lock) {
647      cs = codeSystems.get(system, version);
648    }
649    if (cs == null && locator != null) {
650      locator.findResource(this, system);
651      synchronized (lock) {
652        cs = codeSystems.get(system);
653      }
654    }
655    return cs;
656  } 
657  
658  @Override
659  public CodeSystem fetchSupplementedCodeSystem(String system) {
660    CodeSystem cs = fetchCodeSystem(system);
661    if (cs != null) {
662      List<CodeSystem> supplements = codeSystems.getSupplements(cs);
663      if (supplements.size() > 0) {
664        cs = CodeSystemUtilities.mergeSupplements(cs, supplements);
665      }
666    }
667    return cs;
668  }
669
670  @Override
671  public CodeSystem fetchSupplementedCodeSystem(String system, String version) {
672    CodeSystem cs = fetchCodeSystem(system, version);
673    if (cs != null) {
674      List<CodeSystem> supplements = codeSystems.getSupplements(cs);
675      if (supplements.size() > 0) {
676        cs = CodeSystemUtilities.mergeSupplements(cs, supplements);
677      }
678    }
679    return cs;
680  }
681
682  @Override
683  public boolean supportsSystem(String system) throws TerminologyServiceException {
684    synchronized (lock) {
685      if (codeSystems.has(system) && codeSystems.get(system).getContent() != CodeSystemContentMode.NOTPRESENT) {
686        return true;
687      } else if (supportedCodeSystems.contains(system)) {
688        return true;
689      } else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) {
690        return false;
691      } else {
692        if (noTerminologyServer) {
693          return false;
694        }
695        if (tcc.getTxcaps() == null) {
696          try {
697            logger.logMessage("Terminology server: Check for supported code systems for "+system);
698            final TerminologyCapabilities capabilityStatement = txCache.hasTerminologyCapabilities() ? txCache.getTerminologyCapabilities() : tcc.getClient().getTerminologyCapabilities();
699            txCache.cacheTerminologyCapabilities(capabilityStatement);
700            setTxCaps(capabilityStatement);
701          } catch (Exception e) {
702            if (canRunWithoutTerminology) {
703              noTerminologyServer = true;
704              logger.logMessage("==============!! Running without terminology server !! ==============");
705              if (tcc.getClient() != null) {
706                logger.logMessage("txServer = "+tcc.getClient().getId());
707                logger.logMessage("Error = "+e.getMessage()+"");
708              }
709              logger.logMessage("=====================================================================");
710              return false;
711            } else {
712              e.printStackTrace();
713              throw new TerminologyServiceException(e);
714            }
715          }
716          if (supportedCodeSystems.contains(system)) {
717            return true;
718          }
719        }
720      }
721      return false;
722    }
723  }
724
725
726
727
728
729  protected void txLog(String msg) {
730    if (tlogging ) {
731        logger.logDebugMessage(LogCategory.TX, msg);
732    }
733  }
734
735  // --- expansion support ------------------------------------------------------------------------------------------------------------
736
737  public int getExpandCodesLimit() {
738    return expandCodesLimit;
739  }
740
741  public void setExpandCodesLimit(int expandCodesLimit) {
742    this.expandCodesLimit = expandCodesLimit;
743  }
744
745  @Override
746  public ValueSetExpansionOutcome expandVS(Resource src, ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException {
747    ValueSet vs = null;
748    vs = fetchResource(ValueSet.class, binding.getValueSet(), src);
749    if (vs == null) {
750      throw new FHIRException(formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, binding.getValueSet()));
751    }
752    return expandVS(vs, cacheOk, heirarchical);
753  }
754
755
756  @Override
757  public ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean hierarchical, boolean noInactive) throws TerminologyServiceException {
758    ValueSet vs = new ValueSet();
759    vs.setStatus(PublicationStatus.ACTIVE);
760    vs.setCompose(new ValueSetComposeComponent());
761    vs.getCompose().setInactive(!noInactive);
762    vs.getCompose().getInclude().add(inc);
763    CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
764    ValueSetExpansionOutcome res;
765    res = txCache.getExpansion(cacheToken);
766    if (res != null) {
767      return res;
768    }
769    Parameters p = constructParameters(vs, hierarchical);
770    for (ConceptSetComponent incl : vs.getCompose().getInclude()) {
771      codeSystemsUsed.add(incl.getSystem());
772    }
773    for (ConceptSetComponent incl : vs.getCompose().getExclude()) {
774      codeSystemsUsed.add(incl.getSystem());
775    }
776    
777    if (noTerminologyServer) {
778      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE);
779    }
780    Map<String, String> params = new HashMap<String, String>();
781    params.put("_limit", Integer.toString(expandCodesLimit ));
782    params.put("_incomplete", "true");
783    txLog("$expand on "+txCache.summary(vs));
784    try {
785      ValueSet result = tcc.getClient().expandValueset(vs, p, params);
786      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());  
787    } catch (Exception e) {
788      res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
789      if (txLog != null) {
790        res.setTxLink(txLog.getLastId());
791      }
792    }
793    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
794    return res;
795  }
796
797  @Override
798  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) {
799    if (expParameters == null)
800      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
801    Parameters p = expParameters.copy(); 
802    return expandVS(vs, cacheOk, heirarchical, false, p);
803  }
804
805  @Override
806  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk) {
807    if (expParameters == null)
808      throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
809    Parameters p = expParameters.copy(); 
810    return expandVS(vs, cacheOk, heirarchical, incompleteOk, p);
811  }
812
813  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn)  {
814    if (pIn == null) {
815      throw new Error(formatMessage(I18nConstants.NO_PARAMETERS_PROVIDED_TO_EXPANDVS));
816    }
817    if (vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-time-units") || vs.getUrl().equals("http://hl7.org/fhir/ValueSet/all-distance-units")) {
818      return new ValueSetExpansionOutcome("This value set is not expanded correctly at this time (will be fixed in a future version)", TerminologyServiceErrorClass.VALUESET_UNSUPPORTED);
819    }
820    
821    Parameters p = pIn.copy();
822
823    if (vs.hasExpansion()) {
824      return new ValueSetExpansionOutcome(vs.copy());
825    }
826    if (!vs.hasUrl()) {
827      throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL));
828    }
829    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
830      codeSystemsUsed.add(inc.getSystem());
831    }
832    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
833      codeSystemsUsed.add(inc.getSystem());
834    }
835
836    CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
837    ValueSetExpansionOutcome res;
838    if (cacheOk) {
839      res = txCache.getExpansion(cacheToken);
840      if (res != null) {
841        return res;
842      }
843    }
844    
845    p.setParameter("excludeNested", !hierarchical);
846    if (incompleteOk) {
847      p.setParameter("incomplete-ok", true);      
848    }
849
850    List<String> allErrors = new ArrayList<>();
851    
852    // ok, first we try to expand locally
853    ValueSetExpander vse = constructValueSetExpanderSimple();
854    res = null;
855    try {
856      res = vse.expand(vs, p);
857    } catch (Exception e) {
858      allErrors.addAll(vse.getAllErrors());
859      e.printStackTrace();
860      res = new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
861    }
862    allErrors.addAll(vse.getAllErrors());
863    if (res.getValueset() != null) {
864      if (!res.getValueset().hasUrl()) {
865        throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET));
866      }
867      txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
868      return res;
869    }
870    if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR || isNoTerminologyServer()) { // this class is created specifically to say: don't consult the server
871      return new ValueSetExpansionOutcome(res.getError(), res.getErrorClass());
872    }
873
874    // if that failed, we try to expand on the server
875    if (addDependentResources(p, vs)) {
876      p.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId()));              
877    }
878    
879    if (noTerminologyServer) {
880      return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, allErrors);
881    }
882    Map<String, String> params = new HashMap<String, String>();
883    params.put("_limit", Integer.toString(expandCodesLimit ));
884    params.put("_incomplete", "true");
885    txLog("$expand on "+txCache.summary(vs));
886    try {
887      ValueSet result = tcc.getClient().expandValueset(vs, p, params);
888      if (!result.hasUrl()) {
889        result.setUrl(vs.getUrl());
890      }
891      if (!result.hasUrl()) {
892        throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2));
893      }
894      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());  
895    } catch (Exception e) {
896      res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId());
897    }
898    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
899    return res;
900  }
901
902  private boolean hasTooCostlyExpansion(ValueSet valueset) {
903    return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY);
904  }
905  // --- validate code -------------------------------------------------------------------------------
906  
907  @Override
908  public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display) {
909    assert options != null;
910    Coding c = new Coding(system, version, code, display);
911    return validateCode(options, c, null);
912  }
913
914  @Override
915  public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs) {
916    assert options != null;
917    Coding c = new Coding(system, version, code, display);
918    ValidationResult ret = validateCode(options, "$", c, vs);
919    ret.trimPath("$");
920    return ret;
921  }
922
923  @Override
924  public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs) {
925    assert options != null;
926    Coding c = new Coding(null, code, null);
927    return validateCode(options.withGuessSystem(), c, vs);
928  }
929
930
931  @Override
932  public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
933    if (options == null) {
934      options = ValidationOptions.defaults();
935    }
936    // 1st pass: what is in the cache? 
937    // 2nd pass: What can we do internally 
938    // 3rd pass: hit the server
939    for (CodingValidationRequest t : codes) {
940      t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs, expParameters) : null);
941      if (t.getCoding().hasSystem()) {
942        codeSystemsUsed.add(t.getCoding().getSystem());
943      }
944      if (txCache != null) { 
945        t.setResult(txCache.getValidation(t.getCacheToken()));
946      }
947    }
948    if (options.isUseClient()) {
949      for (CodingValidationRequest t : codes) {
950        if (!t.hasResult()) {
951          try {
952            ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs);
953            vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null);
954            ValidationResult res = vsc.validateCode("Coding", t.getCoding());
955            if (txCache != null) {
956              txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT);
957            }
958            t.setResult(res);
959          } catch (Exception e) {
960          }
961        }
962      }      
963    }  
964
965    for (CodingValidationRequest t : codes) {
966      if (!t.hasResult()) {
967        String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem();
968        if (!options.isUseServer()) {
969         t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null));
970        } else if (unsupportedCodeSystems.contains(codeKey)) {
971          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));      
972        } else if (noTerminologyServer) {
973          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null));
974        }
975      }
976    }
977    
978    if (expParameters == null)
979      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
980    // for those that that failed, we try to validate on the server
981    Bundle batch = new Bundle();
982    batch.setType(BundleType.BATCH);
983    Set<String> systems = new HashSet<>();
984    for (CodingValidationRequest codingValidationRequest : codes) {
985      if (!codingValidationRequest.hasResult()) {
986        Parameters pIn = constructParameters(options, codingValidationRequest, vs);
987        setTerminologyOptions(options, pIn);
988        BundleEntryComponent be = batch.addEntry();
989        be.setResource(pIn);
990        be.getRequest().setMethod(HTTPVerb.POST);
991        be.getRequest().setUrl("CodeSystem/$validate-code");
992        be.setUserData("source", codingValidationRequest);
993        systems.add(codingValidationRequest.getCoding().getSystem());
994      }
995    }
996    if (batch.getEntry().size() > 0) {
997      txLog("$batch validate for "+batch.getEntry().size()+" codes on systems "+systems.toString());
998      if (tcc.getClient() == null) {
999        throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1000      }
1001      if (txLog != null) {
1002        txLog.clearLastId();
1003      }
1004      Bundle resp = tcc.getClient().validateBatch(batch);
1005      if (resp == null) {
1006        throw new FHIRException(formatMessage(I18nConstants.TX_SERVER_NO_BATCH_RESPONSE));          
1007      }      
1008      for (int i = 0; i < batch.getEntry().size(); i++) {
1009        CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData("source");
1010        BundleEntryComponent r = resp.getEntry().get(i);
1011
1012        if (r.getResource() instanceof Parameters) {
1013          t.setResult(processValidationResult((Parameters) r.getResource(), vs == null ? null : vs.getUrl()));
1014          if (txCache != null) {
1015            txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
1016          }
1017        } else {
1018          t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource()), null).setTxLink(txLog == null ? null : txLog.getLastId()));          
1019        }
1020      }
1021    }    
1022  }
1023  
1024  @Override
1025  public void validateCodeBatchByRef(ValidationOptions options, List<? extends CodingValidationRequest> codes, String vsUrl) {
1026    if (options == null) {
1027      options = ValidationOptions.defaults();
1028    }
1029    // 1st pass: what is in the cache? 
1030    // 2nd pass: What can we do internally 
1031    // 3rd pass: hit the server
1032    for (CodingValidationRequest t : codes) {
1033      t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vsUrl, expParameters) : null);
1034      if (t.getCoding().hasSystem()) {
1035        codeSystemsUsed.add(t.getCoding().getSystem());
1036      }
1037      if (txCache != null) { 
1038        t.setResult(txCache.getValidation(t.getCacheToken()));
1039      }
1040    }
1041    if (options.isUseClient()) {
1042      ValueSet vs = fetchResource(ValueSet.class, vsUrl);
1043      if (vs != null) {
1044        for (CodingValidationRequest t : codes) {
1045          if (!t.hasResult()) {
1046            try {
1047              ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs);
1048              vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null);
1049              ValidationResult res = vsc.validateCode("Coding", t.getCoding());
1050              if (txCache != null) {
1051                txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT);
1052              }
1053              t.setResult(res);
1054            } catch (Exception e) {
1055            }
1056          }
1057        }      
1058      }  
1059    }
1060
1061    for (CodingValidationRequest t : codes) {
1062      if (!t.hasResult()) {
1063        String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem();
1064        if (!options.isUseServer()) {
1065         t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null));
1066        } else if (unsupportedCodeSystems.contains(codeKey)) {
1067          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));      
1068        } else if (noTerminologyServer) {
1069          t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null));
1070        }
1071      }
1072    }
1073    
1074    if (expParameters == null)
1075      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1076    // for those that that failed, we try to validate on the server
1077    Bundle batch = new Bundle();
1078    batch.setType(BundleType.BATCH);
1079    Set<String> systems = new HashSet<>();
1080    for (CodingValidationRequest codingValidationRequest : codes) {
1081      if (!codingValidationRequest.hasResult()) {
1082        Parameters pIn = constructParameters(options, codingValidationRequest, vsUrl);
1083        setTerminologyOptions(options, pIn);
1084        BundleEntryComponent be = batch.addEntry();
1085        be.setResource(pIn);
1086        be.getRequest().setMethod(HTTPVerb.POST);
1087        if (vsUrl != null) {
1088          be.getRequest().setUrl("ValueSet/$validate-code");
1089        } else {
1090          be.getRequest().setUrl("CodeSystem/$validate-code");
1091        }
1092        be.setUserData("source", codingValidationRequest);
1093        systems.add(codingValidationRequest.getCoding().getSystem());
1094      }
1095    }
1096    if (batch.getEntry().size() > 0) {
1097      txLog("$batch validate for "+batch.getEntry().size()+" codes on systems "+systems.toString());
1098      if (tcc.getClient() == null) {
1099        throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1100      }
1101      if (txLog != null) {
1102        txLog.clearLastId();
1103      }
1104      Bundle resp = tcc.getClient().validateBatch(batch);
1105      if (resp == null) {
1106        throw new FHIRException(formatMessage(I18nConstants.TX_SERVER_NO_BATCH_RESPONSE));          
1107      }      
1108      for (int i = 0; i < batch.getEntry().size(); i++) {
1109        CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData("source");
1110        BundleEntryComponent r = resp.getEntry().get(i);
1111
1112        if (r.getResource() instanceof Parameters) {
1113          t.setResult(processValidationResult((Parameters) r.getResource(), vsUrl));
1114          if (txCache != null) {
1115            txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
1116          }
1117        } else {
1118          t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource()), null).setTxLink(txLog == null ? null : txLog.getLastId()));          
1119        }
1120      }
1121    }    
1122  }
1123  
1124  private String getResponseText(Resource resource) {
1125    if (resource instanceof OperationOutcome) {
1126      return OperationOutcomeRenderer.toString((OperationOutcome) resource);
1127    }
1128    return "Todo";
1129  }
1130
1131  @Override
1132  public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) {
1133    ValidationContextCarrier ctxt = new ValidationContextCarrier();
1134    return validateCode(options, "Coding", code, vs, ctxt);
1135  }
1136  
1137  public ValidationResult validateCode(ValidationOptions options, String path, Coding code, ValueSet vs) {
1138    ValidationContextCarrier ctxt = new ValidationContextCarrier();
1139    return validateCode(options, path, code, vs, ctxt);
1140  }
1141
1142  private final String getCodeKey(Coding code) {
1143    return code.hasVersion() ? code.getSystem()+"|"+code.getVersion() : code.getSystem();
1144  }
1145
1146  @Override
1147  public ValidationResult validateCode(final ValidationOptions optionsArg, final Coding code, final ValueSet vs, final ValidationContextCarrier ctxt) {
1148    return validateCode(optionsArg, "Coding", code, vs, ctxt); 
1149  }
1150  
1151  public ValidationResult validateCode(final ValidationOptions optionsArg, String path, final Coding code, final ValueSet vs, final ValidationContextCarrier ctxt) {
1152
1153    ValidationOptions options = optionsArg != null ? optionsArg : ValidationOptions.defaults();
1154
1155    if (code.hasSystem()) {
1156      codeSystemsUsed.add(code.getSystem());
1157    }
1158
1159    final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateValidationToken(options, code, vs, expParameters) : null;
1160    ValidationResult res = null;
1161    if (cachingAllowed && txCache != null) {
1162      res = txCache.getValidation(cacheToken);
1163    }
1164    if (res != null) {
1165      updateUnsupportedCodeSystems(res, code, getCodeKey(code));
1166      return res;
1167    }
1168
1169    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1170    Set<String> unknownSystems = new HashSet<>();
1171    
1172    String localError = null;
1173    String localWarning = null;
1174    if (options.isUseClient()) {
1175      // ok, first we try to validate locally
1176      try {
1177        ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs, ctxt);
1178        vsc.setUnknownSystems(unknownSystems);
1179        vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null);
1180        if (!ValueSetUtilities.isServerSide(code.getSystem())) {
1181          res = vsc.validateCode(path, code);
1182          if (txCache != null && cachingAllowed) {
1183            txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
1184          }
1185          return res;
1186        }
1187      } catch (VSCheckerException e) {
1188        if (e.isWarning()) {
1189          localWarning = e.getMessage();
1190        } else {  
1191          localError = e.getMessage();
1192        }
1193        if (e.getIssues() != null) {
1194          issues.addAll(e.getIssues());
1195        }
1196      } catch (TerminologyServiceProtectionException e) {
1197        OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, e.getType());
1198        iss.getDetails().setText(e.getMessage());
1199        issues.add(iss);
1200        return new ValidationResult(IssueSeverity.FATAL, e.getMessage(), e.getError(), issues);
1201      } catch (Exception e) {
1202//        e.printStackTrace();
1203        localError = e.getMessage();
1204      }
1205    }
1206    
1207    if (localError != null && tcc.getClient() == null) {
1208      if (unknownSystems.size() > 0) {
1209        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues).setUnknownSystems(unknownSystems);
1210      } else {
1211        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.UNKNOWN, issues);
1212      }
1213    }
1214    if (localWarning != null && tcc.getClient() == null) {
1215      return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localWarning), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);       
1216    }
1217    if (!options.isUseServer()) {
1218      if (localWarning != null) {
1219        return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localWarning), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);       
1220      } else {
1221        return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localError), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);
1222      }
1223    }
1224    String codeKey = getCodeKey(code);
1225    if (unsupportedCodeSystems.contains(codeKey)) {
1226      return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, code.getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues);      
1227    }
1228    
1229    // if that failed, we try to validate on the server
1230    if (noTerminologyServer) {
1231      return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, issues);
1232    }
1233    String csumm =cachingAllowed && txCache != null ? txCache.summary(code) : null;
1234    if (cachingAllowed && txCache != null) {
1235      txLog("$validate "+csumm+" for "+ txCache.summary(vs));
1236    } else {
1237      txLog("$validate "+csumm+" before cache exists");
1238    }
1239    try {
1240      Parameters pIn = constructParameters(options, code);
1241      res = validateOnServer(vs, pIn, options);
1242    } catch (Exception e) {
1243      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage(), null).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR);
1244    }
1245    if (!res.isOk() && localError != null) {
1246      res.setDiagnostics("Local Error: "+localError.trim()+". Server Error: "+res.getMessage());
1247    }
1248    updateUnsupportedCodeSystems(res, code, codeKey);
1249    if (cachingAllowed && txCache != null) { // we never cache unsupported code systems - we always keep trying (but only once per run)
1250      txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
1251    }
1252    return res;
1253  }
1254
1255  protected ValueSetExpander constructValueSetExpanderSimple() {
1256    return new ValueSetExpander(this, new TerminologyOperationContext(this));
1257  }
1258
1259  protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options,  ValueSet vs,  ValidationContextCarrier ctxt) {
1260    return new ValueSetValidator(this, new TerminologyOperationContext(this), options, vs, ctxt, expParameters, tcc.getTxcaps());
1261  }
1262
1263  protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options,  ValueSet vs) {
1264    return new ValueSetValidator(this, new TerminologyOperationContext(this), options, vs, expParameters, tcc.getTxcaps());
1265  }
1266
1267  protected Parameters constructParameters(ValueSet vs, boolean hierarchical) {
1268    Parameters p = expParameters.copy();
1269    p.setParameter("includeDefinition", false);
1270    p.setParameter("excludeNested", !hierarchical);
1271
1272    boolean cached = addDependentResources(p, vs);
1273    if (cached) {
1274      p.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId()));
1275    }
1276    return p;
1277  }
1278
1279  protected Parameters constructParameters(ValidationOptions options, Coding coding) {
1280    Parameters pIn = new Parameters();
1281    pIn.addParameter().setName("coding").setValue(coding);
1282    if (options.isGuessSystem()) {
1283      pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
1284    }
1285    setTerminologyOptions(options, pIn);
1286    return pIn;
1287  }
1288
1289  protected Parameters constructParameters(ValidationOptions options, CodeableConcept codeableConcept) {
1290    Parameters pIn = new Parameters();
1291    pIn.addParameter().setName("codeableConcept").setValue(codeableConcept);
1292    setTerminologyOptions(options, pIn);
1293    return pIn;
1294  }
1295
1296  protected Parameters constructParameters(ValidationOptions options, CodingValidationRequest codingValidationRequest, ValueSet valueSet) {
1297    Parameters pIn = new Parameters();
1298    pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
1299    if (options.isGuessSystem()) {
1300      pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
1301    }
1302    if (valueSet != null) {
1303      pIn.addParameter().setName("valueSet").setResource(valueSet);
1304    }
1305    pIn.addParameter().setName("profile").setResource(expParameters);
1306    return pIn;
1307  }
1308
1309  protected Parameters constructParameters(ValidationOptions options, CodingValidationRequest codingValidationRequest, String vsUrl) {
1310    Parameters pIn = new Parameters();
1311    pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
1312    if (options.isGuessSystem()) {
1313      pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
1314    }
1315    if (vsUrl != null) {
1316      pIn.addParameter().setName("url").setValue(new CanonicalType(vsUrl));
1317    }
1318    pIn.addParameter().setName("profile").setResource(expParameters);
1319    return pIn;
1320  }
1321
1322  private void updateUnsupportedCodeSystems(ValidationResult res, Coding code, String codeKey) {
1323    if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && !code.hasVersion()) {
1324      unsupportedCodeSystems.add(codeKey);
1325    }
1326  }
1327
1328  private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
1329    if (options.hasLanguages()) {
1330      pIn.addParameter("displayLanguage", options.getLanguages().toString());
1331    }
1332    if (options.getValueSetMode() != ValueSetMode.ALL_CHECKS) {
1333      pIn.addParameter("valueSetMode", options.getValueSetMode().toString());
1334    }
1335    if (options.isVersionFlexible()) {
1336      pIn.addParameter("default-to-latest-version", true);     
1337    }
1338  }
1339
1340  @Override
1341  public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) {
1342    CacheToken cacheToken = txCache.generateValidationToken(options, code, vs, expParameters);
1343    ValidationResult res = null;
1344    if (cachingAllowed) {
1345      res = txCache.getValidation(cacheToken);
1346      if (res != null) {
1347        return res;
1348      }
1349    }
1350    for (Coding c : code.getCoding()) {
1351      if (c.hasSystem()) {
1352        codeSystemsUsed.add(c.getSystem());
1353      }
1354    }
1355    Set<String> unknownSystems = new HashSet<>();
1356
1357    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1358    
1359    String localError = null;
1360    String localWarning = null;
1361    
1362    if (options.isUseClient()) {
1363      // ok, first we try to validate locally
1364      try {
1365        ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs);
1366        vsc.setUnknownSystems(unknownSystems);
1367        vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null);
1368        res = vsc.validateCode("CodeableConcept", code);
1369        if (cachingAllowed) {
1370          txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
1371        }
1372        return res;
1373      } catch (VSCheckerException e) {
1374        if (e.isWarning()) {
1375          localWarning = e.getMessage();
1376        } else {  
1377          localError = e.getMessage();
1378        }
1379        if (e.getIssues() != null) {
1380          issues.addAll(e.getIssues());
1381        }
1382      } catch (TerminologyServiceProtectionException e) {
1383        OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR, e.getType());
1384        iss.getDetails().setText(e.getMessage());
1385        issues.add(iss);
1386        return new ValidationResult(IssueSeverity.FATAL, e.getMessage(), e.getError(), issues);
1387      } catch (Exception e) {
1388//        e.printStackTrace();
1389        localError = e.getMessage();
1390      }
1391    }
1392
1393    if (localError != null && tcc.getClient() == null) {
1394      if (unknownSystems.size() > 0) {
1395        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, issues).setUnknownSystems(unknownSystems);
1396      } else {
1397        return new ValidationResult(IssueSeverity.ERROR, localError, TerminologyServiceErrorClass.UNKNOWN, issues);
1398      }
1399    }
1400    if (localWarning != null && tcc.getClient() == null) {
1401      return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER, localWarning), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, issues);       
1402    }
1403    
1404    if (!options.isUseServer()) {
1405      return new ValidationResult(IssueSeverity.WARNING, "Unable to validate code without using server", TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null);      
1406    }
1407    
1408    // if that failed, we try to validate on the server
1409    if (noTerminologyServer) {
1410      return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE, null);
1411    }
1412    txLog("$validate "+txCache.summary(code)+" for "+ txCache.summary(vs));
1413    try {
1414      Parameters pIn = constructParameters(options, code);
1415      res = validateOnServer(vs, pIn, options);
1416    } catch (Exception e) {
1417      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage(), null).setTxLink(txLog == null ? null : txLog.getLastId());
1418    }
1419    if (cachingAllowed) {
1420      txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
1421    }
1422    return res;
1423  }
1424
1425  protected ValidationResult validateOnServer(ValueSet vs, Parameters pin, ValidationOptions options) throws FHIRException {
1426
1427    if (vs != null) {
1428      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1429        codeSystemsUsed.add(inc.getSystem());
1430      }
1431      for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1432        codeSystemsUsed.add(inc.getSystem());
1433      }
1434    }
1435
1436    addServerValidationParameters(vs, pin, options);
1437
1438    if (txLog != null) {
1439      txLog.clearLastId();
1440    }
1441    if (tcc.getClient() == null) {
1442      throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
1443    }
1444    Parameters pOut;
1445    if (vs == null) {
1446      pOut = tcc.getClient().validateCS(pin);
1447    } else {
1448      pOut = tcc.getClient().validateVS(pin);
1449    }
1450    return processValidationResult(pOut, vs == null ? null : vs.getUrl());
1451  }
1452
1453  protected void addServerValidationParameters(ValueSet vs, Parameters pin, ValidationOptions options) {
1454    boolean cache = false;
1455    if (vs != null) {
1456      if (tcc.isTxCaching() && tcc.getCacheId() != null && vs.getUrl() != null && tcc.getCached().contains(vs.getUrl()+"|"+ vs.getVersion())) {
1457        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+ vs.getVersion() : "")));
1458      } else if (options.getVsAsUrl()){
1459        pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()));
1460      } else {
1461        pin.addParameter().setName("valueSet").setResource(vs);
1462        if (vs.getUrl() != null) {
1463          tcc.getCached().add(vs.getUrl()+"|"+ vs.getVersion());
1464        }
1465      }
1466      cache = true;
1467      addDependentResources(pin, vs);
1468    }
1469    if (cache) {
1470      pin.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId()));
1471    }
1472    for (ParametersParameterComponent pp : pin.getParameter()) {
1473      if (pp.getName().equals("profile")) {
1474        throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT));
1475      }
1476    }
1477    if (expParameters == null) {
1478      throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
1479    }
1480    pin.addParameter().setName("profile").setResource(expParameters);
1481
1482    if (options.isDisplayWarningMode()) {
1483      pin.addParameter("mode","lenient-display-validation");
1484    }
1485  }
1486
1487  private boolean addDependentResources(Parameters pin, ValueSet vs) {
1488    boolean cache = false;
1489    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
1490      cache = addDependentResources(pin, inc, vs) || cache;
1491    }
1492    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
1493      cache = addDependentResources(pin, inc, vs) || cache;
1494    }
1495    return cache;
1496  }
1497
1498  private boolean addDependentResources(Parameters pin, ConceptSetComponent inc, Resource src) {
1499    boolean cache = false;
1500    for (CanonicalType c : inc.getValueSet()) {
1501      ValueSet vs = fetchResource(ValueSet.class, c.getValue(), src);
1502      if (vs != null && !hasCanonicalResource(pin, "tx-resource", vs.getVUrl())) {
1503        cache = checkAddToParams(pin, vs) || cache;
1504        addDependentResources(pin, vs);
1505      }
1506    }
1507    CodeSystem cs = fetchResource(CodeSystem.class, inc.getSystem(), src);
1508    if (cs != null && !hasCanonicalResource(pin, "tx-resource", cs.getVUrl()) && (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
1509      cache = checkAddToParams(pin, cs) || cache;
1510      // todo: supplements
1511    }
1512    return cache;
1513  }
1514
1515  private boolean checkAddToParams(Parameters pin, CanonicalResource cr) {
1516    boolean cache = false;
1517    boolean addToParams = false;
1518    if (tcc.usingCache()) {
1519      if (!tcc.alreadyCached(cr)) {
1520        tcc.addToCache(cr);
1521        System.out.println("add to cache: "+cr.getVUrl());
1522        addToParams = true;
1523        cache = true;
1524      } else {
1525        System.out.println("already cached: "+cr.getVUrl());
1526      }
1527    } else {
1528      addToParams = true;
1529    }
1530    if (addToParams) {
1531      pin.addParameter().setName("tx-resource").setResource(cr);
1532    }
1533    return cache;
1534  }
1535
1536  private boolean hasCanonicalResource(Parameters pin, String name, String vUrl) {
1537    for (ParametersParameterComponent p : pin.getParameter()) {
1538      if (name.equals(p.getName()) && p.hasResource() &&
1539          p.getResource() instanceof CanonicalResource && vUrl.equals(((CanonicalResource) p.getResource()).getVUrl())) {
1540        return true;
1541      }
1542    }
1543    return false;
1544  }
1545
1546  public ValidationResult processValidationResult(Parameters pOut, String vs) {
1547    boolean ok = false;
1548    String message = "No Message returned";
1549    String display = null;
1550    String system = null;
1551    String code = null;
1552    String version = null;
1553    boolean inactive = false;
1554    String status = null;
1555    List<OperationOutcomeIssueComponent> issues = new ArrayList<>();
1556    Set<String> unknownSystems = new HashSet<>();
1557
1558    TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN;
1559    for (ParametersParameterComponent p : pOut.getParameter()) {
1560      if (p.hasValue()) {
1561        if (p.getName().equals("result")) {
1562          ok = ((BooleanType) p.getValue()).getValue().booleanValue();
1563        } else if (p.getName().equals("message")) {
1564          message = p.getValue().primitiveValue();
1565        } else if (p.getName().equals("display")) {
1566          display = p.getValue().primitiveValue();
1567        } else if (p.getName().equals("system")) {
1568          system = ((PrimitiveType<?>) p.getValue()).asStringValue();
1569        } else if (p.getName().equals("version")) {
1570          version = ((PrimitiveType<?>) p.getValue()).asStringValue();
1571        } else if (p.getName().equals("code")) {
1572          code = ((PrimitiveType<?>) p.getValue()).asStringValue();
1573        } else if (p.getName().equals("inactive")) {
1574          inactive = "true".equals(((PrimitiveType<?>) p.getValue()).asStringValue());
1575        } else if (p.getName().equals("status")) {
1576          status = ((PrimitiveType<?>) p.getValue()).asStringValue();
1577        } else if (p.getName().equals("x-caused-by-unknown-system")) {
1578          err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
1579          unknownSystems.add(((PrimitiveType<?>) p.getValue()).asStringValue());      
1580        } else if (p.getName().equals("warning-withdrawn")) {
1581          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
1582          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_WITHDRAWN : I18nConstants.MSG_WITHDRAWN_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));              
1583          issues.add(iss);
1584        } else if (p.getName().equals("warning-deprecated")) {
1585          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
1586          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DEPRECATED : I18nConstants.MSG_DEPRECATED_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));              
1587          issues.add(iss);
1588        } else if (p.getName().equals("warning-retired")) {
1589          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
1590          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_RETIRED : I18nConstants.MSG_RETIRED_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));              
1591          issues.add(iss);
1592        } else if (p.getName().equals("warning-experimental")) {
1593          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
1594          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_EXPERIMENTAL : I18nConstants.MSG_EXPERIMENTAL_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));              
1595          issues.add(iss);
1596        } else if (p.getName().equals("warning-draft")) {
1597          OperationOutcomeIssueComponent iss = new OperationOutcomeIssueComponent(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION, org.hl7.fhir.r5.model.OperationOutcome.IssueType.BUSINESSRULE);
1598          iss.getDetails().setText(formatMessage(vs == null ? I18nConstants.MSG_DRAFT : I18nConstants.MSG_DRAFT_SRC, ((PrimitiveType<?>) p.getValue()).asStringValue(), vs));              
1599          issues.add(iss);
1600        } else if (p.getName().equals("cause")) {
1601          try {
1602            IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue());
1603            if (it == IssueType.UNKNOWN) {
1604              err = TerminologyServiceErrorClass.UNKNOWN;
1605            } else if (it == IssueType.NOTFOUND) {
1606              err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED;
1607            } else if (it == IssueType.NOTSUPPORTED) {
1608              err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED;
1609            } else {
1610              err = null;
1611            }
1612          } catch (FHIRException e) {
1613          }
1614        }
1615      }
1616    }
1617    ValidationResult res = null;
1618    if (!ok) {
1619      res = new ValidationResult(IssueSeverity.ERROR, message+" (from "+tcc.getClient().getId()+")", err, null).setTxLink(txLog.getLastId());
1620    } else if (message != null && !message.equals("No Message returned")) { 
1621      res = new ValidationResult(IssueSeverity.WARNING, message+" (from "+tcc.getClient().getId()+")", system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display, null).setTxLink(txLog.getLastId());
1622    } else if (display != null) {
1623      res = new ValidationResult(system, version, new ConceptDefinitionComponent().setDisplay(display).setCode(code), display).setTxLink(txLog.getLastId());
1624    } else {
1625      res = new ValidationResult(system, version, new ConceptDefinitionComponent().setCode(code), null).setTxLink(txLog.getLastId());
1626    }
1627    res.setIssues(issues);
1628    res.setStatus(inactive, status);
1629    res.setUnknownSystems(unknownSystems);
1630    return res;
1631  }
1632
1633  // --------------------------------------------------------------------------------------------------------------------------------------------------------
1634  
1635  protected void initTS(String cachePath) throws IOException {
1636    if (cachePath != null && !new File(cachePath).exists()) {
1637      Utilities.createDirectory(cachePath);
1638    }
1639    txCache = new TerminologyCache(lock, cachePath);
1640  }
1641
1642  public void clearTSCache(String url) throws Exception {
1643    txCache.removeCS(url);
1644  }
1645
1646  public void clearTS() {
1647    txCache.clear();
1648  }
1649
1650  public boolean isCanRunWithoutTerminology() {
1651    return canRunWithoutTerminology;
1652  }
1653
1654  public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) {
1655    this.canRunWithoutTerminology = canRunWithoutTerminology;
1656  }
1657
1658  public void setLogger(@Nonnull ILoggingService logger) {
1659    this.logger = logger;
1660  }
1661
1662  public Parameters getExpansionParameters() {
1663    return expParameters;
1664  }
1665
1666  public void setExpansionProfile(Parameters expParameters) {
1667    this.expParameters = expParameters;
1668  }
1669
1670  @Override
1671  public boolean isNoTerminologyServer() {
1672    return noTerminologyServer || tcc.getClient() == null;
1673  }
1674
1675  public void setNoTerminologyServer(boolean noTerminologyServer) {
1676    this.noTerminologyServer = noTerminologyServer;
1677  }
1678
1679  public String getName() {
1680    return name;
1681  }
1682
1683  public void setName(String name) {
1684    this.name = name;
1685  }
1686
1687  @Override
1688  public Set<String> getResourceNamesAsSet() {
1689    Set<String> res = new HashSet<String>();
1690    res.addAll(getResourceNames());
1691    return res;
1692  }
1693
1694  public boolean isAllowLoadingDuplicates() {
1695    return allowLoadingDuplicates;
1696  }
1697
1698  public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) {
1699    this.allowLoadingDuplicates = allowLoadingDuplicates;
1700  }
1701
1702  @Override
1703  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
1704    return fetchResourceWithException(class_, uri, null);
1705  }
1706  
1707  public <T extends Resource> T fetchResourceWithException(String cls, String uri) throws FHIRException {
1708    return fetchResourceWithExceptionByVersion(cls, uri, null, null);
1709  }
1710  
1711  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, Resource sourceForReference) throws FHIRException {
1712    return fetchResourceWithExceptionByVersion(class_, uri, null, sourceForReference);
1713  }
1714  
1715  @SuppressWarnings("unchecked")
1716  public <T extends Resource> T fetchResourceWithExceptionByVersion(Class<T> class_, String uri, String version, Resource sourceForReference) throws FHIRException {
1717    if (uri == null) {
1718      return null;
1719    }
1720    
1721    if (QA_CHECK_REFERENCE_SOURCE) {
1722      // it can be tricky to trace the source of a reference correctly. The code isn't water tight,
1723      // particularly around snapshot generation. Enable this code to check that the references are 
1724      // correct (but it's slow)
1725      if (sourceForReference != null && uri.contains("ValueSet")) {
1726        if (!ResourceUtilities.hasURL(uri, sourceForReference)) {
1727          System.out.print("Claimed source doesn't have url in it: "+sourceForReference.fhirType()+"/"+sourceForReference.getIdPart()+" -> "+uri);
1728          System.out.println();
1729        }
1730      }
1731    }
1732   
1733    List<String> pvlist = new ArrayList<>();
1734    if (sourceForReference != null && sourceForReference.getSourcePackage() != null) {
1735      populatePVList(pvlist, sourceForReference.getSourcePackage());
1736    }
1737    
1738    if (class_ == StructureDefinition.class) {
1739      uri = ProfileUtilities.sdNs(uri, null);
1740    }
1741    synchronized (lock) {
1742
1743      if (version == null) {
1744        if (uri.contains("|")) {
1745          version = uri.substring(uri.lastIndexOf("|")+1);
1746          uri = uri.substring(0, uri.lastIndexOf("|"));
1747        }
1748      } else {
1749        assert !uri.contains("|");
1750      }
1751      if (uri.contains("#")) {
1752        uri = uri.substring(0, uri.indexOf("#"));
1753      } 
1754      if (class_ == Resource.class || class_ == null) {
1755        if (structures.has(uri)) {
1756          return (T) structures.get(uri, version, pvlist);
1757        }        
1758        if (guides.has(uri)) {
1759          return (T) guides.get(uri, version, pvlist);
1760        } 
1761        if (capstmts.has(uri)) {
1762          return (T) capstmts.get(uri, version, pvlist);
1763        } 
1764        if (measures.has(uri)) {
1765          return (T) measures.get(uri, version, pvlist);
1766        } 
1767        if (libraries.has(uri)) {
1768          return (T) libraries.get(uri, version, pvlist);
1769        } 
1770        if (valueSets.has(uri)) {
1771          return (T) valueSets.get(uri, version, pvlist);
1772        } 
1773        if (codeSystems.has(uri)) {
1774          return (T) codeSystems.get(uri, version, pvlist);
1775        } 
1776        if (operations.has(uri)) {
1777          return (T) operations.get(uri, version, pvlist);
1778        } 
1779        if (searchParameters.has(uri)) {
1780          return (T) searchParameters.get(uri, version, pvlist);
1781        } 
1782        if (plans.has(uri)) {
1783          return (T) plans.get(uri, version, pvlist);
1784        } 
1785        if (maps.has(uri)) {
1786          return (T) maps.get(uri, version, pvlist);
1787        } 
1788        if (transforms.has(uri)) {
1789          return (T) transforms.get(uri, version, pvlist);
1790        } 
1791        if (actors.has(uri)) {
1792          return (T) transforms.get(uri, version, pvlist);
1793        } 
1794        if (requirements.has(uri)) {
1795          return (T) transforms.get(uri, version, pvlist);
1796        } 
1797        if (questionnaires.has(uri)) {
1798          return (T) questionnaires.get(uri, version, pvlist);
1799        } 
1800
1801        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
1802          for (ResourceProxy r : rt.values()) {
1803            if (uri.equals(r.getUrl())) {
1804              if (version == null || version == r.getResource().getMeta().getVersionId()) {
1805                return (T) r.getResource();
1806              }
1807            }
1808          }            
1809        }
1810        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
1811          return null;
1812        }
1813
1814        // it might be a special URL.
1815//        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
1816//          Resource res = null; // findTxValueSet(uri);
1817//          if (res != null) {
1818//            return (T) res;
1819//          }
1820//        }
1821        return null;      
1822      } else if (class_ == ImplementationGuide.class) {
1823        return (T) guides.get(uri, version, pvlist);
1824      } else if (class_ == CapabilityStatement.class) {
1825        return (T) capstmts.get(uri, version, pvlist);
1826      } else if (class_ == Measure.class) {
1827        return (T) measures.get(uri, version, pvlist);
1828      } else if (class_ == Library.class) {
1829        return (T) libraries.get(uri, version, pvlist);
1830      } else if (class_ == StructureDefinition.class) {
1831        return (T) structures.get(uri, version, pvlist);
1832      } else if (class_ == StructureMap.class) {
1833        return (T) transforms.get(uri, version, pvlist);
1834      } else if (class_ == ValueSet.class) {
1835        return (T) valueSets.get(uri, version, pvlist);
1836      } else if (class_ == CodeSystem.class) {
1837        return (T) codeSystems.get(uri, version, pvlist);
1838      } else if (class_ == ConceptMap.class) {
1839        return (T) maps.get(uri, version, pvlist);
1840      } else if (class_ == ActorDefinition.class) {
1841        return (T) actors.get(uri, version, pvlist);
1842      } else if (class_ == Requirements.class) {
1843        return (T) requirements.get(uri, version, pvlist);
1844      } else if (class_ == PlanDefinition.class) {
1845        return (T) plans.get(uri, version, pvlist);
1846      } else if (class_ == OperationDefinition.class) {
1847        OperationDefinition od = operations.get(uri, version);
1848        return (T) od;
1849      } else if (class_ == Questionnaire.class) {
1850        return (T) questionnaires.get(uri, version, pvlist);
1851      } else if (class_ == SearchParameter.class) {
1852        SearchParameter res = searchParameters.get(uri, version, pvlist);
1853        return (T) res;
1854      }
1855      if (class_ == CodeSystem.class && codeSystems.has(uri)) { 
1856        return (T) codeSystems.get(uri, version, pvlist);
1857      }
1858      if (class_ == ValueSet.class && valueSets.has(uri)) {
1859        return (T) valueSets.get(uri, version, pvlist);
1860      } 
1861      
1862      if (class_ == Questionnaire.class) {
1863        return (T) questionnaires.get(uri, version, pvlist);
1864      } 
1865      if (supportedCodeSystems.contains(uri)) {
1866        return null;
1867      } 
1868      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
1869    }
1870  }
1871
1872  private void populatePVList(List<String> pvlist, PackageInformation sourcePackage) {
1873    pvlist.add(sourcePackage.getVID());
1874    List<String> toadd = new ArrayList<>();
1875    do {
1876      toadd.clear();
1877      for (String s : pvlist) {
1878        PackageInformation pi = packages.get(s);
1879        if (pi != null) {
1880          for (String v : pi.getDependencies()) {
1881            if (!pvlist.contains(v) && !toadd.contains(v)) {
1882              toadd.add(v);
1883            }
1884          }
1885        }        
1886      }
1887      pvlist.addAll(toadd);
1888    } while (toadd.size() > 0);
1889  }
1890
1891  public PackageInformation getPackageForUrl(String uri) {
1892    if (uri == null) {
1893      return null;
1894    }
1895    uri = ProfileUtilities.sdNs(uri, null);
1896
1897    synchronized (lock) {
1898
1899      String version = null;
1900      if (uri.contains("|")) {
1901        version = uri.substring(uri.lastIndexOf("|")+1);
1902        uri = uri.substring(0, uri.lastIndexOf("|"));
1903      }
1904      if (uri.contains("#")) {
1905        uri = uri.substring(0, uri.indexOf("#"));
1906      } 
1907      if (structures.has(uri)) {
1908        return structures.getPackageInfo(uri, version);
1909      }        
1910      if (guides.has(uri)) {
1911        return guides.getPackageInfo(uri, version);
1912      } 
1913      if (capstmts.has(uri)) {
1914        return capstmts.getPackageInfo(uri, version);
1915      } 
1916      if (measures.has(uri)) {
1917        return measures.getPackageInfo(uri, version);
1918      } 
1919      if (libraries.has(uri)) {
1920        return libraries.getPackageInfo(uri, version);
1921      } 
1922      if (valueSets.has(uri)) {
1923        return valueSets.getPackageInfo(uri, version);
1924      } 
1925      if (codeSystems.has(uri)) {
1926        return codeSystems.getPackageInfo(uri, version);
1927      } 
1928      if (operations.has(uri)) {
1929        return operations.getPackageInfo(uri, version);
1930      } 
1931      if (searchParameters.has(uri)) {
1932        return searchParameters.getPackageInfo(uri, version);
1933      } 
1934      if (plans.has(uri)) {
1935        return plans.getPackageInfo(uri, version);
1936      } 
1937      if (maps.has(uri)) {
1938        return maps.getPackageInfo(uri, version);
1939      } 
1940      if (transforms.has(uri)) {
1941        return transforms.getPackageInfo(uri, version);
1942      } 
1943      if (actors.has(uri)) {
1944        return actors.getPackageInfo(uri, version);
1945      } 
1946      if (requirements.has(uri)) {
1947        return requirements.getPackageInfo(uri, version);
1948      } 
1949      if (questionnaires.has(uri)) {
1950        return questionnaires.getPackageInfo(uri, version);
1951      }         
1952      return null;
1953    }
1954  }
1955  
1956  @SuppressWarnings("unchecked")
1957  public <T extends Resource> T fetchResourceWithExceptionByVersion(String cls, String uri, String version, CanonicalResource source) throws FHIRException {
1958    if (uri == null) {
1959      return null;
1960    }
1961   
1962    if ("StructureDefinition".equals(cls)) {
1963      uri = ProfileUtilities.sdNs(uri, null);
1964    }
1965    synchronized (lock) {
1966
1967      if (version == null) {
1968        if (uri.contains("|")) {
1969          version = uri.substring(uri.lastIndexOf("|")+1);
1970          uri = uri.substring(0, uri.lastIndexOf("|"));
1971        }
1972      } else {
1973        boolean b = !uri.contains("|");
1974        assert b;
1975      }
1976      if (uri.contains("#")) {
1977        uri = uri.substring(0, uri.indexOf("#"));
1978      } 
1979      if (cls == null || "Resource".equals(cls)) {
1980        if (structures.has(uri)) {
1981          return (T) structures.get(uri, version);
1982        } 
1983        if (guides.has(uri)) {
1984          return (T) guides.get(uri, version);
1985        } 
1986        if (capstmts.has(uri)) {
1987          return (T) capstmts.get(uri, version);
1988        } 
1989        if (measures.has(uri)) {
1990          return (T) measures.get(uri, version);
1991        } 
1992        if (libraries.has(uri)) {
1993          return (T) libraries.get(uri, version);
1994        } 
1995        if (valueSets.has(uri)) {
1996          return (T) valueSets.get(uri, version);
1997        } 
1998        if (codeSystems.has(uri)) {
1999          return (T) codeSystems.get(uri, version);
2000        } 
2001        if (operations.has(uri)) {
2002          return (T) operations.get(uri, version);
2003        } 
2004        if (searchParameters.has(uri)) {
2005          return (T) searchParameters.get(uri, version);
2006        } 
2007        if (plans.has(uri)) {
2008          return (T) plans.get(uri, version);
2009        } 
2010        if (maps.has(uri)) {
2011          return (T) maps.get(uri, version);
2012        } 
2013        if (transforms.has(uri)) {
2014          return (T) transforms.get(uri, version);
2015        } 
2016        if (actors.has(uri)) {
2017          return (T) actors.get(uri, version);
2018        } 
2019        if (requirements.has(uri)) {
2020          return (T) requirements.get(uri, version);
2021        } 
2022        if (questionnaires.has(uri)) {
2023          return (T) questionnaires.get(uri, version);
2024        } 
2025        for (Map<String, ResourceProxy> rt : allResourcesById.values()) {
2026          for (ResourceProxy r : rt.values()) {
2027            if (uri.equals(r.getUrl())) {
2028              return (T) r.getResource();
2029            }
2030          }            
2031        }
2032      } else if ("ImplementationGuide".equals(cls)) {
2033        return (T) guides.get(uri, version);
2034      } else if ("CapabilityStatement".equals(cls)) {
2035        return (T) capstmts.get(uri, version);
2036      } else if ("Measure".equals(cls)) {
2037        return (T) measures.get(uri, version);
2038      } else if ("Library".equals(cls)) {
2039        return (T) libraries.get(uri, version);
2040      } else if ("StructureDefinition".equals(cls)) {
2041        return (T) structures.get(uri, version);
2042      } else if ("StructureMap".equals(cls)) {
2043        return (T) transforms.get(uri, version);
2044      } else if ("Requirements".equals(cls)) {
2045        return (T) requirements.get(uri, version);
2046      } else if ("ActorDefinition".equals(cls)) {
2047        return (T) actors.get(uri, version);
2048      } else if ("ValueSet".equals(cls)) {
2049        return (T) valueSets.get(uri, version);
2050      } else if ("CodeSystem".equals(cls)) {
2051        return (T) codeSystems.get(uri, version);
2052      } else if ("ConceptMap".equals(cls)) {
2053        return (T) maps.get(uri, version);
2054      } else if ("PlanDefinition".equals(cls)) {
2055        return (T) plans.get(uri, version);
2056      } else if ("OperationDefinition".equals(cls)) {
2057        OperationDefinition od = operations.get(uri, version);
2058        return (T) od;
2059      } else if ("Questionnaire.class".equals(cls)) {
2060        return (T) questionnaires.get(uri, version);
2061      } else if ("SearchParameter.class".equals(cls)) {
2062        SearchParameter res = searchParameters.get(uri, version);
2063        return (T) res;
2064      }
2065      if ("CodeSystem".equals(cls) && codeSystems.has(uri)) {
2066        return (T) codeSystems.get(uri, version);
2067      } 
2068      if ("ValueSet".equals(cls) && valueSets.has(uri)) {
2069        return (T) valueSets.get(uri, version);
2070      } 
2071      
2072      if ("Questionnaire".equals(cls)) {
2073        return (T) questionnaires.get(uri, version);
2074      } 
2075      if (cls == null) {
2076        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) {
2077          return null;
2078        } 
2079
2080        // it might be a special URL.
2081        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
2082          Resource res = null; // findTxValueSet(uri);
2083          if (res != null) {
2084            return (T) res;
2085          } 
2086        }
2087        return null;      
2088      }    
2089      if (supportedCodeSystems.contains(uri)) {
2090        return null;
2091      } 
2092      throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri));
2093    }
2094  }
2095  
2096  @SuppressWarnings("unchecked")
2097  public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_) {
2098
2099    List<T> res = new ArrayList<>();
2100
2101    synchronized (lock) {
2102
2103      if (class_ == Resource.class || class_ == DomainResource.class || class_ == CanonicalResource.class || class_ == null) {
2104        res.addAll((List<T>) structures.getList());
2105        res.addAll((List<T>) guides.getList());
2106        res.addAll((List<T>) capstmts.getList());
2107        res.addAll((List<T>) measures.getList());
2108        res.addAll((List<T>) libraries.getList());
2109        res.addAll((List<T>) valueSets.getList());
2110        res.addAll((List<T>) codeSystems.getList());
2111        res.addAll((List<T>) operations.getList());
2112        res.addAll((List<T>) searchParameters.getList());
2113        res.addAll((List<T>) plans.getList());
2114        res.addAll((List<T>) maps.getList());
2115        res.addAll((List<T>) transforms.getList());
2116        res.addAll((List<T>) questionnaires.getList());
2117        res.addAll((List<T>) systems.getList());
2118        res.addAll((List<T>) actors.getList());
2119        res.addAll((List<T>) requirements.getList());
2120      } else if (class_ == ImplementationGuide.class) {
2121        res.addAll((List<T>) guides.getList());
2122      } else if (class_ == CapabilityStatement.class) {
2123        res.addAll((List<T>) capstmts.getList());
2124      } else if (class_ == Measure.class) {
2125        res.addAll((List<T>) measures.getList());
2126      } else if (class_ == Library.class) {
2127        res.addAll((List<T>) libraries.getList());
2128      } else if (class_ == StructureDefinition.class) {
2129        res.addAll((List<T>) structures.getList());
2130      } else if (class_ == StructureMap.class) {
2131        res.addAll((List<T>) transforms.getList());
2132      } else if (class_ == ValueSet.class) {
2133        res.addAll((List<T>) valueSets.getList());
2134      } else if (class_ == CodeSystem.class) {
2135        res.addAll((List<T>) codeSystems.getList());
2136      } else if (class_ == NamingSystem.class) {
2137        res.addAll((List<T>) systems.getList());
2138      } else if (class_ == ActorDefinition.class) {
2139        res.addAll((List<T>) actors.getList());
2140      } else if (class_ == Requirements.class) {
2141        res.addAll((List<T>) requirements.getList());
2142      } else if (class_ == ConceptMap.class) {
2143        res.addAll((List<T>) maps.getList());
2144      } else if (class_ == PlanDefinition.class) {
2145        res.addAll((List<T>) plans.getList());
2146      } else if (class_ == OperationDefinition.class) {
2147        res.addAll((List<T>) operations.getList());
2148      } else if (class_ == Questionnaire.class) {
2149        res.addAll((List<T>) questionnaires.getList());
2150      } else if (class_ == SearchParameter.class) {
2151        res.addAll((List<T>) searchParameters.getList());
2152      }
2153    }
2154    return res;
2155  }
2156
2157  private Set<String> notCanonical = new HashSet<String>();
2158
2159  protected IWorkerContextManager.IPackageLoadingTracker packageTracker;
2160  private boolean forPublication;
2161  private boolean cachingAllowed = true;
2162
2163  @Override
2164  public Resource fetchResourceById(String type, String uri) {
2165    synchronized (lock) {
2166      String[] parts = uri.split("\\/");
2167      if (!Utilities.noString(type) && parts.length == 1) {
2168        if (allResourcesById.containsKey(type)) {
2169          return allResourcesById.get(type).get(parts[0]).getResource();
2170        } else {
2171          return null;
2172        }
2173      }
2174      if (parts.length >= 2) {
2175        if (!Utilities.noString(type)) {
2176          if (!type.equals(parts[parts.length-2])) { 
2177            throw new Error(formatMessage(I18nConstants.RESOURCE_TYPE_MISMATCH_FOR___, type, uri));
2178          }
2179        }
2180        return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]).getResource();
2181      } else {
2182        throw new Error(formatMessage(I18nConstants.UNABLE_TO_PROCESS_REQUEST_FOR_RESOURCE_FOR___, type, uri));
2183      }
2184    }
2185  }
2186
2187  public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource sourceForReference) {
2188    try {
2189      return fetchResourceWithException(class_, uri, sourceForReference);
2190    } catch (FHIRException e) {
2191      throw new Error(e);
2192    }    
2193  }
2194  
2195  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
2196    try {
2197      return fetchResourceWithException(class_, uri, null);
2198    } catch (FHIRException e) {
2199      throw new Error(e);
2200    }
2201  }
2202  
2203  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
2204    try {
2205      return fetchResourceWithExceptionByVersion(class_, uri, version, null);
2206    } catch (FHIRException e) {
2207      throw new Error(e);
2208    }
2209  }
2210  
2211  @Override
2212  public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
2213    try {
2214      return fetchResourceWithException(class_, uri) != null;
2215    } catch (Exception e) {
2216      return false;
2217    }
2218  }
2219
2220  public <T extends Resource> boolean hasResource(String cls, String uri) {
2221    try {
2222      return fetchResourceWithException(cls, uri) != null;
2223    } catch (Exception e) {
2224      return false;
2225    }
2226  }
2227
2228  public <T extends Resource> boolean hasResourceVersion(Class<T> class_, String uri, String version) {
2229    try {
2230      return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
2231    } catch (Exception e) {
2232      return false;
2233    }
2234  }
2235
2236  public <T extends Resource> boolean hasResourceVersion(String cls, String uri, String version) {
2237    try {
2238      return fetchResourceWithExceptionByVersion(cls, uri, version, null) != null;
2239    } catch (Exception e) {
2240      return false;
2241    }
2242  }
2243
2244
2245  public TranslationServices translator() {
2246    return translator;
2247  }
2248
2249  public void setTranslator(TranslationServices translator) {
2250    this.translator = translator;
2251  }
2252  
2253  public class NullTranslator implements TranslationServices {
2254
2255    @Override
2256    public String translate(String context, String value, String targetLang) {
2257      return value;
2258    }
2259
2260    @Override
2261    public String translate(String context, String value) {
2262      return value;
2263    }
2264
2265    @Override
2266    public String toStr(float value) {
2267      return null;
2268    }
2269
2270    @Override
2271    public String toStr(Date value) {
2272      return null;
2273    }
2274
2275    @Override
2276    public String translateAndFormat(String contest, String lang, String value, Object... args) {
2277      return String.format(value, args);
2278    }
2279
2280    @Override
2281    public Map<String, String> translations(String value) {
2282      // TODO Auto-generated method stub
2283      return null;
2284    }
2285
2286    @Override
2287    public Set<String> listTranslations(String category) {
2288      // TODO Auto-generated method stub
2289      return null;
2290    }
2291
2292  }
2293  
2294  public void reportStatus(JsonObject json) {
2295    synchronized (lock) {
2296      json.addProperty("codeystem-count", codeSystems.size());
2297      json.addProperty("valueset-count", valueSets.size());
2298      json.addProperty("conceptmap-count", maps.size());
2299      json.addProperty("transforms-count", transforms.size());
2300      json.addProperty("structures-count", structures.size());
2301      json.addProperty("guides-count", guides.size());
2302      json.addProperty("statements-count", capstmts.size());
2303      json.addProperty("measures-count", measures.size());
2304      json.addProperty("libraries-count", libraries.size());
2305    }
2306  }
2307
2308
2309  public void dropResource(Resource r) throws FHIRException {
2310    dropResource(r.fhirType(), r.getId());   
2311  }
2312
2313  public void dropResource(String fhirType, String id) {
2314    synchronized (lock) {
2315
2316      Map<String, ResourceProxy> map = allResourcesById.get(fhirType);
2317      if (map == null) {
2318        map = new HashMap<String, ResourceProxy>();
2319        allResourcesById.put(fhirType, map);
2320      }
2321      if (map.containsKey(id)) {
2322        map.remove(id); // this is a challenge because we might have more than one resource with this id (different versions)
2323      }
2324
2325      if (fhirType.equals("StructureDefinition")) {
2326        structures.drop(id);
2327      } else if (fhirType.equals("ImplementationGuide")) {
2328        guides.drop(id);
2329      } else if (fhirType.equals("CapabilityStatement")) {
2330        capstmts.drop(id);
2331      } else if (fhirType.equals("Measure")) {
2332        measures.drop(id);
2333      } else if (fhirType.equals("Library")) {
2334        libraries.drop(id);
2335      } else if (fhirType.equals("ValueSet")) {
2336        valueSets.drop(id);
2337      } else if (fhirType.equals("CodeSystem")) {
2338        codeSystems.drop(id);
2339      } else if (fhirType.equals("OperationDefinition")) {
2340        operations.drop(id);
2341      } else if (fhirType.equals("Questionnaire")) {
2342        questionnaires.drop(id);
2343      } else if (fhirType.equals("ConceptMap")) {
2344        maps.drop(id);
2345      } else if (fhirType.equals("StructureMap")) {
2346        transforms.drop(id);
2347      } else if (fhirType.equals("NamingSystem")) {
2348        systems.drop(id);
2349        systemUrlMap = null;
2350      } else if (fhirType.equals("ActorDefinition")) {
2351        actors.drop(id);
2352      } else if (fhirType.equals("Requirements")) {
2353        requirements.drop(id);
2354      }
2355    }
2356  }
2357
2358  private <T extends CanonicalResource> void dropMetadataResource(Map<String, T> map, String id) {
2359    T res = map.get(id);
2360    if (res != null) {
2361      map.remove(id);
2362      if (map.containsKey(res.getUrl())) {
2363        map.remove(res.getUrl());
2364      }
2365      if (res.getVersion() != null) {
2366        if (map.containsKey(res.getUrl()+"|"+res.getVersion())) {
2367          map.remove(res.getUrl()+"|"+res.getVersion());
2368        }
2369      }
2370    }
2371  }
2372
2373  
2374  public String listSupportedSystems() {
2375    synchronized (lock) {
2376      String sl = null;
2377      for (String s : supportedCodeSystems) {
2378        sl = sl == null ? s : sl + "\r\n" + s;
2379      }
2380      return sl;
2381    }
2382  }
2383
2384
2385  public int totalCount() {
2386    synchronized (lock) {
2387      return valueSets.size() +  maps.size() + structures.size() + transforms.size();
2388    }
2389  }
2390  
2391  public List<ConceptMap> listMaps() {
2392    List<ConceptMap> m = new ArrayList<ConceptMap>();
2393    synchronized (lock) {
2394      maps.listAll(m);
2395    }
2396    return m;
2397  }
2398  
2399  public List<StructureDefinition> listStructures() {
2400    List<StructureDefinition> m = new ArrayList<StructureDefinition>();
2401    synchronized (lock) {
2402      structures.listAll(m);    
2403    }
2404    return m;
2405  }
2406
2407  public StructureDefinition getStructure(String code) {
2408    synchronized (lock) {
2409      return structures.get(code);
2410    }
2411  }
2412
2413  private String getUri(NamingSystem ns) {
2414    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
2415      if (id.getType() == NamingSystemIdentifierType.URI) {
2416        return id.getValue();
2417      }
2418    }
2419    return null;
2420  }
2421
2422  private boolean hasOid(NamingSystem ns, String oid) {
2423    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
2424      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) {
2425        return true;
2426      }
2427    }
2428    return false;
2429  }
2430
2431  public void cacheVS(JsonObject json, Map<String, ValidationResult> t) {
2432    synchronized (lock) {
2433      validationCache.put(json.get("url").getAsString(), t);
2434    }
2435  }
2436
2437  public SearchParameter getSearchParameter(String code) {
2438    synchronized (lock) {
2439      return searchParameters.get(code);
2440    }
2441  }
2442
2443  @Override
2444  public ILoggingService getLogger() {
2445    return logger;
2446  }
2447
2448  @Override
2449  public StructureDefinition fetchTypeDefinition(String typeName) {
2450    if (Utilities.isAbsoluteUrl(typeName)) {
2451      return fetchResource(StructureDefinition.class, typeName);
2452    } else {
2453      Set<StructureDefinition> types = new HashSet<>();
2454      types.addAll(fetchTypeDefinitions(typeName));
2455      types.removeIf(sd -> sd.getDerivation() == TypeDerivationRule.CONSTRAINT);
2456       if (types.size() == 0) {
2457        return null; // throw new FHIRException("Unresolved type "+typeName+" (0)");
2458      } else if (types.size() == 1) {
2459        return types.iterator().next(); 
2460      } else { 
2461        types.removeIf(sd -> !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/"));
2462        if (types.size() != 1) {
2463          throw new FHIRException("Ambiguous type "+typeName+" ("+types.toString()+") (contact Grahame Grieve for investigation)");
2464        } else  {
2465          return types.iterator().next(); 
2466        } 
2467      }
2468    }
2469  }
2470  
2471  @Override
2472  public List<StructureDefinition> fetchTypeDefinitions(String typeName) {
2473    List<StructureDefinition> res = new ArrayList<>();
2474    structures.listAll(res);
2475    res.removeIf(sd -> !sd.hasType() || !(sd.getType().equals(typeName) || sd.getTypeTail().equals(typeName)));
2476    return res;
2477  }
2478
2479  public boolean isTlogging() {
2480    return tlogging;
2481  }
2482
2483  public void setTlogging(boolean tlogging) {
2484    this.tlogging = tlogging;
2485  }
2486
2487  public UcumService getUcumService() {
2488    return ucumService;
2489  }
2490
2491  public void setUcumService(UcumService ucumService) {
2492    this.ucumService = ucumService;
2493  }
2494
2495  public String getLinkForUrl(String corePath, String url) {
2496    if (url == null) {
2497      return null;
2498    }
2499    
2500    if (codeSystems.has(url)) {
2501      return codeSystems.get(url).getWebPath();
2502    }
2503
2504    if (valueSets.has(url)) {
2505      return valueSets.get(url).getWebPath();
2506    }
2507
2508    if (maps.has(url)) {
2509      return maps.get(url).getWebPath();
2510    }
2511    
2512    if (transforms.has(url)) {
2513      return transforms.get(url).getWebPath();
2514    }
2515    
2516    if (actors.has(url)) {
2517      return actors.get(url).getWebPath();
2518    }
2519    
2520    if (requirements.has(url)) {
2521      return requirements.get(url).getWebPath();
2522    }
2523    
2524    if (structures.has(url)) {
2525      return structures.get(url).getWebPath();
2526    }
2527    
2528    if (guides.has(url)) {
2529      return guides.get(url).getWebPath();
2530    }
2531    
2532    if (capstmts.has(url)) {
2533      return capstmts.get(url).getWebPath();
2534    }
2535    
2536    if (measures.has(url)) {
2537      return measures.get(url).getWebPath();
2538    }
2539
2540    if (libraries.has(url)) {
2541      return libraries.get(url).getWebPath();
2542    }
2543
2544    if (searchParameters.has(url)) {
2545      return searchParameters.get(url).getWebPath();
2546    }
2547        
2548    if (questionnaires.has(url)) {
2549      return questionnaires.get(url).getWebPath();
2550    }
2551
2552    if (operations.has(url)) {
2553      return operations.get(url).getWebPath();
2554    }
2555    
2556    if (plans.has(url)) {
2557      return plans.get(url).getWebPath();
2558    }
2559
2560    if (url.equals("http://loinc.org")) {
2561      return corePath+"loinc.html";
2562    }
2563    if (url.equals("http://unitsofmeasure.org")) {
2564      return corePath+"ucum.html";
2565    } 
2566    if (url.equals("http://snomed.info/sct")) {
2567      return corePath+"snomed.html";
2568    } 
2569    return null;
2570  }
2571
2572  public List<ImplementationGuide> allImplementationGuides() {
2573    List<ImplementationGuide> res = new ArrayList<>();
2574    guides.listAll(res);
2575    return res;
2576  }
2577
2578  @Override
2579  public Set<String> getBinaryKeysAsSet() { return binaries.keySet(); }
2580
2581  @Override
2582  public boolean hasBinaryKey(String binaryKey) {
2583    return binaries.containsKey(binaryKey);
2584  }
2585
2586  @Override
2587  public byte[] getBinaryForKey(String binaryKey) {
2588    return binaries.get(binaryKey);
2589  }
2590
2591  public void finishLoading(boolean genSnapshots) {
2592    if (!hasResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Base")) {
2593      cacheResource(ProfileUtilities.makeBaseDefinition(version));
2594    }
2595    if(genSnapshots) {
2596      for (StructureDefinition sd : listStructures()) {
2597        try {
2598          if (sd.getSnapshot().isEmpty()) { 
2599            new ContextUtilities(this).generateSnapshot(sd);
2600            //          new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
2601          }
2602        } catch (Exception e) {
2603          System.out.println("Unable to generate snapshot @1 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
2604          if (logger.isDebugLogging()) {
2605            e.printStackTrace();          
2606          }
2607        }
2608      }  
2609    }
2610    codeSystems.setVersion(version);
2611    valueSets.setVersion(version);
2612    maps.setVersion(version);
2613    transforms.setVersion(version);
2614    structures.setVersion(version);
2615    measures.setVersion(version);
2616    libraries.setVersion(version);
2617    guides.setVersion(version);
2618    capstmts.setVersion(version);
2619    searchParameters.setVersion(version);
2620    questionnaires.setVersion(version);
2621    operations.setVersion(version);
2622    plans.setVersion(version);
2623    systems.setVersion(version);
2624    actors.setVersion(version);
2625    requirements.setVersion(version);
2626  }
2627
2628  protected String tail(String url) {
2629    if (Utilities.noString(url)) {
2630      return "noname";
2631    }
2632    if (url.contains("/")) {
2633      return url.substring(url.lastIndexOf("/")+1);
2634    }
2635    return url;
2636  }
2637  
2638  public int getClientRetryCount() {
2639    return tcc.getClient() == null ? 0 : tcc.getClient().getRetryCount();
2640  }
2641  
2642  public IWorkerContext setClientRetryCount(int value) {
2643    if (tcc.getClient() != null) {
2644      tcc.getClient().setRetryCount(value);
2645    }
2646    return this;
2647  }
2648
2649  public ITerminologyClient getTxClient() {
2650    return tcc.getClient();
2651  }
2652
2653  public String getCacheId() {
2654    return tcc.getCacheId();
2655  }
2656
2657  public void setCacheId(String cacheId) {
2658    tcc.setCacheId(cacheId);
2659  }
2660
2661  public TerminologyCapabilities getTxCaps() {
2662    return tcc.getTxcaps();
2663  }
2664
2665  public void setTxCaps(TerminologyCapabilities txCaps) {
2666    this.tcc.setTxcaps(txCaps);
2667    if (txCaps != null) {
2668      for (TerminologyCapabilitiesExpansionParameterComponent t : tcc.getTxcaps().getExpansion().getParameter()) {
2669        if ("cache-id".equals(t.getName())) {
2670          tcc.setTxCaching(true);
2671        }
2672      }
2673      for (TerminologyCapabilitiesCodeSystemComponent tccs : tcc.getTxcaps().getCodeSystem()) {
2674        supportedCodeSystems.add(tccs.getUri());
2675      }
2676    }
2677  }
2678
2679  public TimeTracker clock() {
2680    return clock;
2681  }
2682 
2683
2684  public int countAllCaches() {
2685    return codeSystems.size() + valueSets.size() + maps.size() + transforms.size() + structures.size() + measures.size() + libraries.size() + 
2686        guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() + 
2687        systems.size()+ actors.size()+ requirements.size();
2688  }
2689
2690  public Set<String> getCodeSystemsUsed() {
2691    return codeSystemsUsed ;
2692  }
2693 
2694  public IWorkerContextManager.ICanonicalResourceLocator getLocator() {
2695    return locator;
2696  }
2697
2698  public void setLocator(IWorkerContextManager.ICanonicalResourceLocator locator) {
2699    this.locator = locator;
2700  }
2701
2702  public String getUserAgent() {
2703    return userAgent;
2704  }
2705
2706  protected void setUserAgent(String userAgent) {
2707    this.userAgent = userAgent;
2708    if (tcc.getClient() != null)
2709      tcc.getClient().setUserAgent(userAgent);
2710  }
2711
2712
2713  public IWorkerContextManager.IPackageLoadingTracker getPackageTracker() {
2714    return packageTracker;
2715  }
2716  
2717  public IWorkerContext setPackageTracker(IWorkerContextManager.IPackageLoadingTracker packageTracker) {
2718    this.packageTracker = packageTracker;
2719    return this;
2720  }
2721  
2722
2723  @Override
2724  public PEBuilder getProfiledElementBuilder(PEElementPropertiesPolicy elementProps, boolean fixedProps) {
2725    // TODO Auto-generated method stub
2726    return new PEBuilder(this, elementProps, fixedProps);
2727  }
2728  
2729  public boolean isForPublication() {
2730    return forPublication;
2731  }
2732  
2733  public void setForPublication(boolean value) {
2734    forPublication = value;
2735  }
2736
2737  public boolean isCachingAllowed() {
2738    return cachingAllowed;
2739  }
2740
2741  public void setCachingAllowed(boolean cachingAllowed) {
2742    this.cachingAllowed = cachingAllowed;
2743  }
2744
2745}