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