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