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