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