001package org.hl7.fhir.r4.context;
002
003import java.io.FileNotFoundException;
004import java.io.IOException;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Date;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Locale;
012import java.util.Map;
013import java.util.ResourceBundle;
014import java.util.Set;
015
016import org.apache.commons.lang3.StringUtils;
017import org.fhir.ucum.UcumService;
018import org.hl7.fhir.exceptions.DefinitionException;
019import org.hl7.fhir.exceptions.FHIRException;
020import org.hl7.fhir.exceptions.TerminologyServiceException;
021import org.hl7.fhir.r4.conformance.ProfileUtilities;
022import org.hl7.fhir.r4.context.TerminologyCache.CacheToken;
023import org.hl7.fhir.r4.model.BooleanType;
024import org.hl7.fhir.r4.model.CapabilityStatement;
025import org.hl7.fhir.r4.model.CodeSystem;
026import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
027import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
028import org.hl7.fhir.r4.model.CodeableConcept;
029import org.hl7.fhir.r4.model.Coding;
030import org.hl7.fhir.r4.model.ConceptMap;
031import org.hl7.fhir.r4.model.Constants;
032import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
033import org.hl7.fhir.r4.model.ImplementationGuide;
034import org.hl7.fhir.r4.model.IntegerType;
035import org.hl7.fhir.r4.model.MetadataResource;
036import org.hl7.fhir.r4.model.NamingSystem;
037import org.hl7.fhir.r4.model.NamingSystem.NamingSystemIdentifierType;
038import org.hl7.fhir.r4.model.NamingSystem.NamingSystemUniqueIdComponent;
039import org.hl7.fhir.r4.model.OperationDefinition;
040import org.hl7.fhir.r4.model.Parameters;
041import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
042import org.hl7.fhir.r4.model.PlanDefinition;
043import org.hl7.fhir.r4.model.Questionnaire;
044import org.hl7.fhir.r4.model.Reference;
045import org.hl7.fhir.r4.model.Resource;
046import org.hl7.fhir.r4.model.SearchParameter;
047import org.hl7.fhir.r4.model.StringType;
048import org.hl7.fhir.r4.model.StructureDefinition;
049import org.hl7.fhir.r4.model.StructureMap;
050import org.hl7.fhir.r4.model.TerminologyCapabilities;
051import org.hl7.fhir.r4.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent;
052import org.hl7.fhir.r4.model.ValueSet;
053import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
054import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent;
055import org.hl7.fhir.r4.terminologies.TerminologyClient;
056import org.hl7.fhir.r4.terminologies.ValueSetCheckerSimple;
057import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
058import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
059import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple;
060import org.hl7.fhir.r4.utils.ToolingExtensions;
061import org.hl7.fhir.r4.model.DomainResource;
062import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
063import org.hl7.fhir.utilities.OIDUtilities;
064import org.hl7.fhir.utilities.TranslationServices;
065import org.hl7.fhir.utilities.UUIDUtilities;
066import org.hl7.fhir.utilities.Utilities;
067import org.hl7.fhir.utilities.i18n.I18nBase;
068import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
069import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
070import org.hl7.fhir.utilities.validation.ValidationOptions;
071
072/*
073  Copyright (c) 2011+, HL7, Inc.
074  All rights reserved.
075  
076  Redistribution and use in source and binary forms, with or without modification, 
077  are permitted provided that the following conditions are met:
078    
079   * Redistributions of source code must retain the above copyright notice, this 
080     list of conditions and the following disclaimer.
081   * Redistributions in binary form must reproduce the above copyright notice, 
082     this list of conditions and the following disclaimer in the documentation 
083     and/or other materials provided with the distribution.
084   * Neither the name of HL7 nor the names of its contributors may be used to 
085     endorse or promote products derived from this software without specific 
086     prior written permission.
087  
088  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
089  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
090  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
091  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
092  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
093  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
094  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
095  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
096  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
097  POSSIBILITY OF SUCH DAMAGE.
098  
099 */
100
101import com.google.gson.JsonObject;
102
103@MarkedToMoveToAdjunctPackage
104public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext {
105
106  private Object lock = new Object(); // used as a lock for the data that follows
107
108  private Map<String, Map<String, Resource>> allResourcesById = new HashMap<String, Map<String, Resource>>();
109  // all maps are to the full URI
110  private Map<String, CodeSystem> codeSystems = new HashMap<String, CodeSystem>();
111  private Set<String> supportedCodeSystems = new HashSet<String>();
112  private Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>();
113  private Map<String, ConceptMap> maps = new HashMap<String, ConceptMap>();
114  protected Map<String, StructureMap> transforms = new HashMap<String, StructureMap>();
115  private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>();
116  private Map<String, ImplementationGuide> guides = new HashMap<String, ImplementationGuide>();
117  private Map<String, CapabilityStatement> capstmts = new HashMap<String, CapabilityStatement>();
118  private Map<String, SearchParameter> searchParameters = new HashMap<String, SearchParameter>();
119  private Map<String, Questionnaire> questionnaires = new HashMap<String, Questionnaire>();
120  private Map<String, OperationDefinition> operations = new HashMap<String, OperationDefinition>();
121  private Map<String, PlanDefinition> plans = new HashMap<String, PlanDefinition>();
122  private List<NamingSystem> systems = new ArrayList<NamingSystem>();
123  private UcumService ucumService;
124
125  protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String, ValidationResult>>();
126  protected String tsServer;
127  protected String name;
128  private boolean allowLoadingDuplicates;
129
130  protected TerminologyClient txClient;
131  protected HTMLClientLogger txLog;
132  private TerminologyCapabilities txcaps;
133  private boolean canRunWithoutTerminology;
134  protected boolean noTerminologyServer;
135  private int expandCodesLimit = 1000;
136  protected ILoggingService logger;
137  protected Parameters expParameters;
138  private TranslationServices translator = new NullTranslator();
139  protected TerminologyCache txCache;
140
141  private boolean tlogging = true;
142  private Locale locale;
143  private ResourceBundle i18Nmessages;
144
145  public BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException {
146    super();
147    txCache = new TerminologyCache(lock, null);
148  }
149
150  public BaseWorkerContext(Map<String, CodeSystem> codeSystems, Map<String, ValueSet> valueSets,
151      Map<String, ConceptMap> maps, Map<String, StructureDefinition> profiles, Map<String, ImplementationGuide> guides)
152      throws FileNotFoundException, IOException, FHIRException {
153    super();
154    this.codeSystems = codeSystems;
155    this.valueSets = valueSets;
156    this.maps = maps;
157    this.structures = profiles;
158    this.guides = guides;
159    txCache = new TerminologyCache(lock, null);
160  }
161
162  protected void copy(BaseWorkerContext other) {
163    synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet
164      allResourcesById.putAll(other.allResourcesById);
165      translator = other.translator;
166      codeSystems.putAll(other.codeSystems);
167      txcaps = other.txcaps;
168      valueSets.putAll(other.valueSets);
169      maps.putAll(other.maps);
170      transforms.putAll(other.transforms);
171      structures.putAll(other.structures);
172      searchParameters.putAll(other.searchParameters);
173      plans.putAll(other.plans);
174      questionnaires.putAll(other.questionnaires);
175      operations.putAll(other.operations);
176      systems.addAll(other.systems);
177      guides.putAll(other.guides);
178      capstmts.putAll(other.capstmts);
179
180      allowLoadingDuplicates = other.allowLoadingDuplicates;
181      tsServer = other.tsServer;
182      name = other.name;
183      txClient = other.txClient;
184      txLog = other.txLog;
185      txcaps = other.txcaps;
186      canRunWithoutTerminology = other.canRunWithoutTerminology;
187      noTerminologyServer = other.noTerminologyServer;
188      if (other.txCache != null)
189        txCache = other.txCache.copy();
190      expandCodesLimit = other.expandCodesLimit;
191      logger = other.logger;
192      expParameters = other.expParameters;
193    }
194  }
195
196  public void cacheResource(Resource r) throws FHIRException {
197    synchronized (lock) {
198      Map<String, Resource> map = allResourcesById.get(r.fhirType());
199      if (map == null) {
200        map = new HashMap<String, Resource>();
201        allResourcesById.put(r.fhirType(), map);
202      }
203      map.put(r.getId(), r);
204
205      if (r instanceof MetadataResource) {
206        MetadataResource m = (MetadataResource) r;
207        String url = m.getUrl();
208        if (!allowLoadingDuplicates && hasResource(r.getClass(), url))
209          throw new DefinitionException("Duplicate Resource " + url);
210        if (r instanceof StructureDefinition)
211          seeMetadataResource((StructureDefinition) m, structures, false);
212        else if (r instanceof ValueSet)
213          seeMetadataResource((ValueSet) m, valueSets, false);
214        else if (r instanceof CodeSystem)
215          seeMetadataResource((CodeSystem) m, codeSystems, false);
216        else if (r instanceof ImplementationGuide)
217          seeMetadataResource((ImplementationGuide) m, guides, false);
218        else if (r instanceof CapabilityStatement)
219          seeMetadataResource((CapabilityStatement) m, capstmts, false);
220        else if (r instanceof SearchParameter)
221          seeMetadataResource((SearchParameter) m, searchParameters, false);
222        else if (r instanceof PlanDefinition)
223          seeMetadataResource((PlanDefinition) m, plans, false);
224        else if (r instanceof OperationDefinition)
225          seeMetadataResource((OperationDefinition) m, operations, false);
226        else if (r instanceof Questionnaire)
227          seeMetadataResource((Questionnaire) m, questionnaires, true);
228        else if (r instanceof ConceptMap)
229          seeMetadataResource((ConceptMap) m, maps, false);
230        else if (r instanceof StructureMap)
231          seeMetadataResource((StructureMap) m, transforms, false);
232        else if (r instanceof NamingSystem)
233          systems.add((NamingSystem) r);
234      }
235    }
236  }
237
238  /*
239   * Compare business versions, returning "true" if the candidate newer version is
240   * in fact newer than the oldVersion Comparison will work for strictly numeric
241   * versions as well as multi-level versions separated by ., -, _, : or space
242   * Failing that, it will do unicode-based character ordering. E.g. 1.5.3 <
243   * 1.14.3 2017-3-10 < 2017-12-7 A3 < T2
244   */
245  private boolean laterVersion(String newVersion, String oldVersion) {
246    // Compare business versions, retur
247    newVersion = newVersion.trim();
248    oldVersion = oldVersion.trim();
249    if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion))
250      return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion);
251    else if (hasDelimiter(newVersion, oldVersion, "."))
252      return laterDelimitedVersion(newVersion, oldVersion, "\\.");
253    else if (hasDelimiter(newVersion, oldVersion, "-"))
254      return laterDelimitedVersion(newVersion, oldVersion, "\\-");
255    else if (hasDelimiter(newVersion, oldVersion, "_"))
256      return laterDelimitedVersion(newVersion, oldVersion, "\\_");
257    else if (hasDelimiter(newVersion, oldVersion, ":"))
258      return laterDelimitedVersion(newVersion, oldVersion, "\\:");
259    else if (hasDelimiter(newVersion, oldVersion, " "))
260      return laterDelimitedVersion(newVersion, oldVersion, "\\ ");
261    else {
262      return newVersion.compareTo(oldVersion) > 0;
263    }
264  }
265
266  /*
267   * Returns true if both strings include the delimiter and have the same number
268   * of occurrences of it
269   */
270  private boolean hasDelimiter(String s1, String s2, String delimiter) {
271    return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length;
272  }
273
274  private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) {
275    String[] newParts = newVersion.split(delimiter);
276    String[] oldParts = oldVersion.split(delimiter);
277    for (int i = 0; i < newParts.length; i++) {
278      if (!newParts[i].equals(oldParts[i]))
279        return laterVersion(newParts[i], oldParts[i]);
280    }
281    // This should never happen
282    throw new Error(
283        "Delimited versions have exact match for delimiter '" + delimiter + "' : " + newParts + " vs " + oldParts);
284  }
285
286  protected <T extends MetadataResource> void seeMetadataResource(T r, Map<String, T> map, boolean addId)
287      throws FHIRException {
288    if (addId)
289      map.put(r.getId(), r); // todo: why?
290    if (!map.containsKey(r.getUrl()))
291      map.put(r.getUrl(), r);
292    else {
293      // If this resource already exists, see if it's the newest business version. The
294      // default resource to return if not qualified is the most recent business
295      // version
296      MetadataResource existingResource = (MetadataResource) map.get(r.getUrl());
297      if (r.hasVersion() && existingResource.hasVersion() && !r.getVersion().equals(existingResource.getVersion())) {
298        if (laterVersion(r.getVersion(), existingResource.getVersion())) {
299          map.remove(r.getUrl());
300          map.put(r.getUrl(), r);
301        }
302      } else
303        map.remove(r.getUrl());
304      map.put(r.getUrl(), r);
305//        throw new FHIRException("Multiple declarations of resource with same canonical URL (" + r.getUrl() + ") and version (" + (r.hasVersion() ? r.getVersion() : "" ) + ")");
306    }
307    if (r.hasVersion())
308      map.put(r.getUrl() + "|" + r.getVersion(), r);
309  }
310
311  @Override
312  public CodeSystem fetchCodeSystem(String system) {
313    synchronized (lock) {
314      return codeSystems.get(system);
315    }
316  }
317
318  @Override
319  public boolean supportsSystem(String system) throws TerminologyServiceException {
320    synchronized (lock) {
321      if (codeSystems.containsKey(system) && codeSystems.get(system).getContent() != CodeSystemContentMode.NOTPRESENT)
322        return true;
323      else if (supportedCodeSystems.contains(system))
324        return true;
325      else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com")
326          || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:"))
327        return false;
328      else {
329        if (noTerminologyServer)
330          return false;
331        if (txcaps == null) {
332          try {
333            log("Terminology server: Check for supported code systems for " + system);
334            txcaps = txClient.getTerminologyCapabilities();
335          } catch (Exception e) {
336            if (canRunWithoutTerminology) {
337              noTerminologyServer = true;
338              log("==============!! Running without terminology server !! ==============");
339              if (txClient != null) {
340                log("txServer = " + txClient.getAddress());
341                log("Error = " + e.getMessage() + "");
342              }
343              log("=====================================================================");
344              return false;
345            } else
346              throw new TerminologyServiceException(e);
347          }
348          if (txcaps != null) {
349            for (TerminologyCapabilitiesCodeSystemComponent tccs : txcaps.getCodeSystem()) {
350              supportedCodeSystems.add(tccs.getUri());
351            }
352          }
353          if (supportedCodeSystems.contains(system))
354            return true;
355        }
356      }
357      return false;
358    }
359  }
360
361  private void log(String message) {
362    if (logger != null)
363      logger.logMessage(message);
364    else
365      System.out.println(message);
366  }
367
368  protected void tlog(String msg) {
369    if (tlogging)
370      System.out.println("-tx cache miss: " + msg);
371  }
372
373  // --- expansion support
374  // ------------------------------------------------------------------------------------------------------------
375
376  public int getExpandCodesLimit() {
377    return expandCodesLimit;
378  }
379
380  public void setExpandCodesLimit(int expandCodesLimit) {
381    this.expandCodesLimit = expandCodesLimit;
382  }
383
384  @Override
385  public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk,
386      boolean heirarchical) throws FHIRException {
387    ValueSet vs = null;
388    vs = fetchResource(ValueSet.class, binding.getValueSet());
389    if (vs == null)
390      throw new FHIRException("Unable to resolve value Set " + binding.getValueSet());
391    return expandVS(vs, cacheOk, heirarchical);
392  }
393
394  @Override
395  public ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean heirachical)
396      throws TerminologyServiceException {
397    ValueSet vs = new ValueSet();
398    vs.setCompose(new ValueSetComposeComponent());
399    vs.getCompose().getInclude().add(inc);
400    CacheToken cacheToken = txCache.generateExpandToken(vs, heirachical);
401    ValueSetExpansionOutcome res;
402    res = txCache.getExpansion(cacheToken);
403    if (res != null)
404      return res;
405    Parameters p = expParameters.copy();
406    p.setParameter("includeDefinition", false);
407    p.setParameter("excludeNested", !heirachical);
408
409    if (noTerminologyServer)
410      return new ValueSetExpansionOutcome("Error expanding ValueSet: running without terminology services",
411          TerminologyServiceErrorClass.NOSERVICE);
412
413    p.addParameter().setName("_limit").setValue(new IntegerType(expandCodesLimit));
414    p.addParameter().setName("_incomplete").setValue(new BooleanType("true"));
415    
416    tlog("$expand on " + txCache.summary(vs));
417    try {
418      ValueSet result = txClient.expandValueset(vs, p);
419      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
420    } catch (Exception e) {
421      res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(),
422          TerminologyServiceErrorClass.UNKNOWN);
423      if (txLog != null)
424        res.setTxLink(txLog.getLastId());
425    }
426    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
427    return res;
428
429  }
430
431  @Override
432  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) {
433    if (expParameters == null)
434      throw new Error("No Expansion Parameters provided");
435    Parameters p = expParameters.copy();
436    return expandVS(vs, cacheOk, heirarchical, p);
437  }
438
439  public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, Parameters p) {
440    if (p == null)
441      throw new Error("No Parameters provided to expandVS");
442    if (vs.hasExpansion()) {
443      return new ValueSetExpansionOutcome(vs.copy());
444    }
445    if (!vs.hasUrl())
446      throw new Error("no value set");
447
448    CacheToken cacheToken = txCache.generateExpandToken(vs, heirarchical);
449    ValueSetExpansionOutcome res;
450    if (cacheOk) {
451      res = txCache.getExpansion(cacheToken);
452      if (res != null)
453        return res;
454    }
455    p.setParameter("includeDefinition", false);
456    p.setParameter("excludeNested", !heirarchical);
457
458    // ok, first we try to expand locally
459    try {
460      ValueSetExpanderSimple vse = new ValueSetExpanderSimple(this);
461      res = vse.doExpand(vs, p);
462      if (!res.getValueset().hasUrl())
463        throw new Error("no url in expand value set");
464      txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
465      return res;
466    } catch (Exception e) {
467    }
468
469    // if that failed, we try to expand on the server
470    if (noTerminologyServer)
471      return new ValueSetExpansionOutcome("Error expanding ValueSet: running without terminology services",
472          TerminologyServiceErrorClass.NOSERVICE);
473
474    p.addParameter().setName("_limit").setValue(new IntegerType(expandCodesLimit));
475    p.addParameter().setName("_incomplete").setValue(new BooleanType("true"));
476    
477    tlog("$expand on " + txCache.summary(vs));
478    try {
479      ValueSet result = txClient.expandValueset(vs, p);
480      if (result != null) {
481        if (!result.hasUrl())
482          result.setUrl(vs.getUrl());
483        if (!result.hasUrl())
484          throw new Error("no url in expand value set 2");
485      }
486      res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
487    } catch (Exception e) {
488      res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(),
489          TerminologyServiceErrorClass.UNKNOWN).setTxLink(txLog == null ? null : txLog.getLastId());
490    }
491    txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
492    return res;
493  }
494
495  private boolean hasTooCostlyExpansion(ValueSet valueset) {
496    return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(),
497        "http://hl7.org/fhir/StructureDefinition/valueset-toocostly");
498  }
499  // --- validate code
500  // -------------------------------------------------------------------------------
501
502  @Override
503  public ValidationResult validateCode(ValidationOptions options, String system, String code, String display) {
504    Coding c = new Coding(system, code, display);
505    return validateCode(options, c, null);
506  }
507
508  @Override
509  public ValidationResult validateCode(ValidationOptions options, String system, String code, String display,
510      ValueSet vs) {
511    Coding c = new Coding(system, code, display);
512    return validateCode(options, c, vs);
513  }
514
515  @Override
516  public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs) {
517    Coding c = new Coding(null, code, null);
518    return doValidateCode(options, c, vs, true);
519  }
520
521  @Override
522  public ValidationResult validateCode(ValidationOptions options, String system, String code, String display,
523      ConceptSetComponent vsi) {
524    Coding c = new Coding(system, code, display);
525    ValueSet vs = new ValueSet();
526    vs.setUrl(UUIDUtilities.makeUuidUrn());
527    vs.getCompose().addInclude(vsi);
528    return validateCode(options, c, vs);
529  }
530
531  @Override
532  public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) {
533    return doValidateCode(options, code, vs, false);
534  }
535
536  public ValidationResult doValidateCode(ValidationOptions options, Coding code, ValueSet vs, boolean inferSystem) {
537    CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null;
538    ValidationResult res = null;
539    if (txCache != null)
540      res = txCache.getValidation(cacheToken);
541    if (res != null)
542      return res;
543
544    // ok, first we try to validate locally
545    try {
546      ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this);
547      res = vsc.validateCode(code);
548      if (txCache != null)
549        txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
550      return res;
551    } catch (Exception e) {
552    }
553
554    // if that failed, we try to validate on the server
555    if (noTerminologyServer)
556      return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services",
557          TerminologyServiceErrorClass.NOSERVICE);
558    String csumm = txCache != null ? txCache.summary(code) : null;
559    if (txCache != null)
560      tlog("$validate " + csumm + " for " + txCache.summary(vs));
561    else
562      tlog("$validate " + csumm + " before cache exists");
563    try {
564      Parameters pIn = new Parameters();
565      pIn.addParameter().setName("coding").setValue(code);
566      if (inferSystem)
567        pIn.addParameter().setName("inferSystem").setValue(new BooleanType(true));
568      if (options != null)
569        setTerminologyOptions(options, pIn);
570      res = validateOnServer(vs, pIn);
571    } catch (Exception e) {
572      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage())
573          .setTxLink(txLog == null ? null : txLog.getLastId());
574    }
575    if (txCache != null)
576      txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
577    return res;
578  }
579
580  private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
581    if (options != null && options.hasLanguages()) {
582      pIn.addParameter("displayLanguage", options.getLanguages().toString());
583    }
584  }
585
586  @Override
587  public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) {
588    CacheToken cacheToken = txCache.generateValidationToken(options, code, vs);
589    ValidationResult res = txCache.getValidation(cacheToken);
590    if (res != null)
591      return res;
592
593    // ok, first we try to validate locally
594    try {
595      ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this);
596      res = vsc.validateCode(code);
597      txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
598      return res;
599    } catch (Exception e) {
600    }
601
602    // if that failed, we try to validate on the server
603    if (noTerminologyServer)
604      return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services",
605          TerminologyServiceErrorClass.NOSERVICE);
606    tlog("$validate " + txCache.summary(code) + " for " + txCache.summary(vs));
607    try {
608      Parameters pIn = new Parameters();
609      pIn.addParameter().setName("codeableConcept").setValue(code);
610      if (options != null)
611        setTerminologyOptions(options, pIn);
612      res = validateOnServer(vs, pIn);
613    } catch (Exception e) {
614      res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage())
615          .setTxLink(txLog.getLastId());
616    }
617    txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT);
618    return res;
619  }
620
621  private ValidationResult validateOnServer(ValueSet vs, Parameters pin) throws FHIRException {
622    if (vs != null)
623      pin.addParameter().setName("valueSet").setResource(vs);
624    for (ParametersParameterComponent pp : pin.getParameter())
625      if (pp.getName().equals("profile"))
626        throw new Error("Can only specify profile in the context");
627    if (expParameters == null)
628      throw new Error("No ExpansionProfile provided");
629    pin.addParameter().setName("profile").setResource(expParameters);
630    txLog.clearLastId();
631    Parameters pOut;
632    if (vs == null)
633      pOut = txClient.validateCS(pin);
634    else
635      pOut = txClient.validateVS(pin);
636    boolean ok = false;
637    String message = "No Message returned";
638    String display = null;
639    TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN;
640    for (ParametersParameterComponent p : pOut.getParameter()) {
641      if (p.getName().equals("result"))
642        ok = ((BooleanType) p.getValue()).getValue().booleanValue();
643      else if (p.getName().equals("message"))
644        message = ((StringType) p.getValue()).getValue();
645      else if (p.getName().equals("display"))
646        display = ((StringType) p.getValue()).getValue();
647      else if (p.getName().equals("cause")) {
648        try {
649          IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue());
650          if (it == IssueType.UNKNOWN)
651            err = TerminologyServiceErrorClass.UNKNOWN;
652          else if (it == IssueType.NOTSUPPORTED)
653            err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED;
654        } catch (FHIRException e) {
655        }
656      }
657    }
658    if (!ok)
659      return new ValidationResult(IssueSeverity.ERROR, message, err).setTxLink(txLog.getLastId())
660          .setTxLink(txLog.getLastId());
661    else if (message != null && !message.equals("No Message returned"))
662      return new ValidationResult(IssueSeverity.WARNING, message, new ConceptDefinitionComponent().setDisplay(display))
663          .setTxLink(txLog.getLastId()).setTxLink(txLog.getLastId());
664    else if (display != null)
665      return new ValidationResult(new ConceptDefinitionComponent().setDisplay(display)).setTxLink(txLog.getLastId())
666          .setTxLink(txLog.getLastId());
667    else
668      return new ValidationResult(new ConceptDefinitionComponent()).setTxLink(txLog.getLastId())
669          .setTxLink(txLog.getLastId());
670  }
671
672  // --------------------------------------------------------------------------------------------------------------------------------------------------------
673
674  public void initTS(String cachePath) throws Exception {
675    txCache = new TerminologyCache(lock, cachePath);
676  }
677
678  @Override
679  public List<ConceptMap> findMapsForSource(String url) throws FHIRException {
680    synchronized (lock) {
681      List<ConceptMap> res = new ArrayList<ConceptMap>();
682      for (ConceptMap map : maps.values())
683        if (((Reference) map.getSource()).getReference().equals(url))
684          res.add(map);
685      return res;
686    }
687  }
688
689  public boolean isCanRunWithoutTerminology() {
690    return canRunWithoutTerminology;
691  }
692
693  public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) {
694    this.canRunWithoutTerminology = canRunWithoutTerminology;
695  }
696
697  public void setLogger(ILoggingService logger) {
698    this.logger = logger;
699  }
700
701  public Parameters getExpansionParameters() {
702    return expParameters;
703  }
704
705  public void setExpansionProfile(Parameters expParameters) {
706    this.expParameters = expParameters;
707  }
708
709  @Override
710  public boolean isNoTerminologyServer() {
711    return noTerminologyServer;
712  }
713
714  public String getName() {
715    return name;
716  }
717
718  public void setName(String name) {
719    this.name = name;
720  }
721
722  @Override
723  public Set<String> getResourceNamesAsSet() {
724    Set<String> res = new HashSet<String>();
725    res.addAll(getResourceNames());
726    return res;
727  }
728
729  public boolean isAllowLoadingDuplicates() {
730    return allowLoadingDuplicates;
731  }
732
733  public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) {
734    this.allowLoadingDuplicates = allowLoadingDuplicates;
735  }
736
737  @SuppressWarnings("unchecked")
738  @Override
739  public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
740    if (class_ == StructureDefinition.class)
741      uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs());
742    synchronized (lock) {
743
744      if (uri.startsWith("http:") || uri.startsWith("https:")) {
745        String version = null;
746        if (uri.contains("|")) {
747          version = uri.substring(uri.lastIndexOf("|") + 1);
748          uri = uri.substring(0, uri.lastIndexOf("|"));
749        }
750        if (uri.contains("#"))
751          uri = uri.substring(0, uri.indexOf("#"));
752        if (class_ == Resource.class || class_ == null) {
753          if (structures.containsKey(uri))
754            return (T) structures.get(uri);
755          if (guides.containsKey(uri))
756            return (T) guides.get(uri);
757          if (capstmts.containsKey(uri))
758            return (T) capstmts.get(uri);
759          if (valueSets.containsKey(uri))
760            return (T) valueSets.get(uri);
761          if (codeSystems.containsKey(uri))
762            return (T) codeSystems.get(uri);
763          if (operations.containsKey(uri))
764            return (T) operations.get(uri);
765          if (searchParameters.containsKey(uri))
766            return (T) searchParameters.get(uri);
767          if (plans.containsKey(uri))
768            return (T) plans.get(uri);
769          if (maps.containsKey(uri))
770            return (T) maps.get(uri);
771          if (transforms.containsKey(uri))
772            return (T) transforms.get(uri);
773          if (questionnaires.containsKey(uri))
774            return (T) questionnaires.get(uri);
775          for (Map<String, Resource> rt : allResourcesById.values()) {
776            for (Resource r : rt.values()) {
777              if (r instanceof MetadataResource) {
778                MetadataResource mr = (MetadataResource) r;
779                if (uri.equals(mr.getUrl()))
780                  return (T) mr;
781              }
782            }
783          }
784          return null;
785        } else if (class_ == ImplementationGuide.class) {
786          return (T) guides.get(uri);
787        } else if (class_ == CapabilityStatement.class) {
788          return (T) capstmts.get(uri);
789        } else if (class_ == StructureDefinition.class) {
790          return (T) structures.get(uri);
791        } else if (class_ == StructureMap.class) {
792          return (T) transforms.get(uri);
793        } else if (class_ == ValueSet.class) {
794          if (valueSets.containsKey(uri + "|" + version))
795            return (T) valueSets.get(uri + "|" + version);
796          else
797            return (T) valueSets.get(uri);
798        } else if (class_ == CodeSystem.class) {
799          if (codeSystems.containsKey(uri + "|" + version))
800            return (T) codeSystems.get(uri + "|" + version);
801          else
802            return (T) codeSystems.get(uri);
803        } else if (class_ == ConceptMap.class) {
804          return (T) maps.get(uri);
805        } else if (class_ == PlanDefinition.class) {
806          return (T) plans.get(uri);
807        } else if (class_ == OperationDefinition.class) {
808          OperationDefinition od = operations.get(uri);
809          return (T) od;
810        } else if (class_ == SearchParameter.class) {
811          SearchParameter res = searchParameters.get(uri);
812          if (res == null) {
813            StringBuilder b = new StringBuilder();
814            for (String s : searchParameters.keySet()) {
815              b.append(s);
816              b.append("\r\n");
817            }
818          }
819          return (T) res;
820        }
821      }
822      if (class_ == CodeSystem.class && codeSystems.containsKey(uri))
823        return (T) codeSystems.get(uri);
824
825      if (class_ == Questionnaire.class)
826        return (T) questionnaires.get(uri);
827      if (class_ == null) {
828        if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet"))
829          return null;
830
831        // it might be a special URL.
832        if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) {
833          Resource res = null; // findTxValueSet(uri);
834          if (res != null)
835            return (T) res;
836        }
837        return null;
838      }
839      if (supportedCodeSystems.contains(uri))
840        return null;
841      throw new FHIRException("not done yet: can't fetch " + uri);
842    }
843  }
844
845  private Set<String> notCanonical = new HashSet<String>();
846
847  private String overrideVersionNs;
848
849//  private MetadataResource findTxValueSet(String uri) {
850//    MetadataResource res = expansionCache.getStoredResource(uri);
851//    if (res != null)
852//      return res;
853//    synchronized (lock) {
854//      if (notCanonical.contains(uri))
855//        return null;
856//    }
857//    try {
858//      tlog("get canonical "+uri);
859//      res = txServer.getCanonical(ValueSet.class, uri);
860//    } catch (Exception e) {
861//      synchronized (lock) {
862//        notCanonical.add(uri);
863//      }
864//      return null;
865//    }
866//    if (res != null)
867//      try {
868//        expansionCache.storeResource(res);
869//      } catch (IOException e) {
870//      }
871//    return res;
872//  }
873
874  @Override
875  public Resource fetchResourceById(String type, String uri) {
876    synchronized (lock) {
877      String[] parts = uri.split("\\/");
878      if (!Utilities.noString(type) && parts.length == 1) {
879        if (allResourcesById.containsKey(type))
880          return allResourcesById.get(type).get(parts[0]);
881        else
882          return null;
883      }
884      if (parts.length >= 2) {
885        if (!Utilities.noString(type))
886          if (!type.equals(parts[parts.length - 2]))
887            throw new Error("Resource type mismatch for " + type + " / " + uri);
888        return allResourcesById.get(parts[parts.length - 2]).get(parts[parts.length - 1]);
889      } else
890        throw new Error("Unable to process request for resource for " + type + " / " + uri);
891    }
892  }
893
894  public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
895    try {
896      return fetchResourceWithException(class_, uri);
897    } catch (FHIRException e) {
898      throw new Error(e);
899    }
900  }
901
902  @Override
903  public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source) {
904    return fetchResource(class_, uri);
905  }
906
907  @Override
908  public List<StructureDefinition> fetchTypeDefinitions(String n) {
909    List<StructureDefinition> types = new ArrayList<>();
910    for (StructureDefinition sd : fetchResourcesByType(StructureDefinition.class)) {
911      if (n.equals(sd.getTypeTail())) {
912        types.add(sd);
913      }
914    }
915    return types;
916  }
917
918  public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
919    try {
920      return fetchResourceWithException(class_, uri+"|"+version);
921    } catch (FHIRException e) {
922      throw new Error(e);
923    }
924  }
925
926  @Override
927  public <T extends Resource> boolean hasResource(Class<T> class_, String uri) {
928    try {
929      return fetchResourceWithException(class_, uri) != null;
930    } catch (Exception e) {
931      return false;
932    }
933  }
934
935  public TranslationServices translator() {
936    return translator;
937  }
938
939  public void setTranslator(TranslationServices translator) {
940    this.translator = translator;
941  }
942
943  public class NullTranslator implements TranslationServices {
944
945    @Override
946    public String translate(String context, String value, String targetLang) {
947      return value;
948    }
949
950    @Override
951    public String translate(String context, String value) {
952      return value;
953    }
954
955    @Override
956    public String toStr(float value) {
957      return null;
958    }
959
960    @Override
961    public String toStr(Date value) {
962      return null;
963    }
964
965    @Override
966    public String translateAndFormat(String contest, String lang, String value, Object... args) {
967      return String.format(value, args);
968    }
969
970    @Override
971    public Map<String, String> translations(String value) {
972      // TODO Auto-generated method stub
973      return null;
974    }
975
976    @Override
977    public Set<String> listTranslations(String category) {
978      // TODO Auto-generated method stub
979      return null;
980    }
981
982  }
983
984  public void reportStatus(JsonObject json) {
985    synchronized (lock) {
986      json.addProperty("codeystem-count", codeSystems.size());
987      json.addProperty("valueset-count", valueSets.size());
988      json.addProperty("conceptmap-count", maps.size());
989      json.addProperty("transforms-count", transforms.size());
990      json.addProperty("structures-count", structures.size());
991      json.addProperty("guides-count", guides.size());
992      json.addProperty("statements-count", capstmts.size());
993    }
994  }
995
996  public void dropResource(Resource r) throws FHIRException {
997    dropResource(r.fhirType(), r.getId());
998  }
999
1000  public void dropResource(String fhirType, String id) {
1001    synchronized (lock) {
1002
1003      Map<String, Resource> map = allResourcesById.get(fhirType);
1004      if (map == null) {
1005        map = new HashMap<String, Resource>();
1006        allResourcesById.put(fhirType, map);
1007      }
1008      if (map.containsKey(id))
1009        map.remove(id);
1010
1011      if (fhirType.equals("StructureDefinition"))
1012        dropMetadataResource(structures, id);
1013      else if (fhirType.equals("ImplementationGuide"))
1014        dropMetadataResource(guides, id);
1015      else if (fhirType.equals("CapabilityStatement"))
1016        dropMetadataResource(capstmts, id);
1017      else if (fhirType.equals("ValueSet"))
1018        dropMetadataResource(valueSets, id);
1019      else if (fhirType.equals("CodeSystem"))
1020        dropMetadataResource(codeSystems, id);
1021      else if (fhirType.equals("OperationDefinition"))
1022        dropMetadataResource(operations, id);
1023      else if (fhirType.equals("Questionnaire"))
1024        dropMetadataResource(questionnaires, id);
1025      else if (fhirType.equals("ConceptMap"))
1026        dropMetadataResource(maps, id);
1027      else if (fhirType.equals("StructureMap"))
1028        dropMetadataResource(transforms, id);
1029      else if (fhirType.equals("NamingSystem"))
1030        for (int i = systems.size() - 1; i >= 0; i--) {
1031          if (systems.get(i).getId().equals(id))
1032            systems.remove(i);
1033        }
1034    }
1035  }
1036
1037  private <T extends MetadataResource> void dropMetadataResource(Map<String, T> map, String id) {
1038    T res = map.get(id);
1039    if (res != null) {
1040      map.remove(id);
1041      if (map.containsKey(res.getUrl()))
1042        map.remove(res.getUrl());
1043      if (res.getVersion() != null)
1044        if (map.containsKey(res.getUrl() + "|" + res.getVersion()))
1045          map.remove(res.getUrl() + "|" + res.getVersion());
1046    }
1047  }
1048
1049  @Override
1050  public List<MetadataResource> allConformanceResources() {
1051    synchronized (lock) {
1052      List<MetadataResource> result = new ArrayList<MetadataResource>();
1053      result.addAll(structures.values());
1054      result.addAll(guides.values());
1055      result.addAll(capstmts.values());
1056      result.addAll(codeSystems.values());
1057      result.addAll(valueSets.values());
1058      result.addAll(maps.values());
1059      result.addAll(transforms.values());
1060      result.addAll(plans.values());
1061      result.addAll(questionnaires.values());
1062      return result;
1063    }
1064  }
1065
1066  public String listSupportedSystems() {
1067    synchronized (lock) {
1068      String sl = null;
1069      for (String s : supportedCodeSystems)
1070        sl = sl == null ? s : sl + "\r\n" + s;
1071      return sl;
1072    }
1073  }
1074
1075  public int totalCount() {
1076    synchronized (lock) {
1077      return valueSets.size() + maps.size() + structures.size() + transforms.size();
1078    }
1079  }
1080
1081  public List<ConceptMap> listMaps() {
1082    List<ConceptMap> m = new ArrayList<ConceptMap>();
1083    synchronized (lock) {
1084      m.addAll(maps.values());
1085    }
1086    return m;
1087  }
1088
1089  public List<StructureMap> listTransforms() {
1090    List<StructureMap> m = new ArrayList<StructureMap>();
1091    synchronized (lock) {
1092      m.addAll(transforms.values());
1093    }
1094    return m;
1095  }
1096
1097  public StructureMap getTransform(String code) {
1098    synchronized (lock) {
1099      return transforms.get(code);
1100    }
1101  }
1102
1103  public List<StructureDefinition> listStructures() {
1104    List<StructureDefinition> m = new ArrayList<StructureDefinition>();
1105    synchronized (lock) {
1106      m.addAll(structures.values());
1107    }
1108    return m;
1109  }
1110
1111  public StructureDefinition getStructure(String code) {
1112    synchronized (lock) {
1113      return structures.get(code);
1114    }
1115  }
1116
1117  @Override
1118  public String oid2Uri(String oid) {
1119    synchronized (lock) {
1120      String uri = OIDUtilities.getUriForOid(oid);
1121      if (uri != null)
1122        return uri;
1123      for (NamingSystem ns : systems) {
1124        if (hasOid(ns, oid)) {
1125          uri = getUri(ns);
1126          if (uri != null)
1127            return null;
1128        }
1129      }
1130    }
1131    return null;
1132  }
1133
1134  private String getUri(NamingSystem ns) {
1135    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
1136      if (id.getType() == NamingSystemIdentifierType.URI)
1137        return id.getValue();
1138    }
1139    return null;
1140  }
1141
1142  private boolean hasOid(NamingSystem ns, String oid) {
1143    for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
1144      if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid))
1145        return true;
1146    }
1147    return false;
1148  }
1149
1150  public void cacheVS(JsonObject json, Map<String, ValidationResult> t) {
1151    synchronized (lock) {
1152      validationCache.put(json.get("url").getAsString(), t);
1153    }
1154  }
1155
1156  public SearchParameter getSearchParameter(String code) {
1157    synchronized (lock) {
1158      return searchParameters.get(code);
1159    }
1160  }
1161
1162  @Override
1163  public String getOverrideVersionNs() {
1164    return overrideVersionNs;
1165  }
1166
1167  @Override
1168  public void setOverrideVersionNs(String value) {
1169    overrideVersionNs = value;
1170  }
1171
1172  @Override
1173  public ILoggingService getLogger() {
1174    return logger;
1175  }
1176
1177  @Override
1178  public StructureDefinition fetchTypeDefinition(String typeName) {
1179    if (Utilities.isAbsoluteUrl(typeName))
1180      return fetchResource(StructureDefinition.class, typeName);
1181    else
1182      return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
1183  }
1184
1185
1186  public boolean isPrimitiveType(String type) {
1187    return Utilities.existsInList(type, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "uuid", "xhtml", "url", "canonical");
1188  }
1189
1190  public boolean isDataType(String type) {
1191    return Utilities.existsInList(type, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 
1192        "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
1193  }
1194  
1195  
1196  public boolean isTlogging() {
1197    return tlogging;
1198  }
1199
1200  public void setTlogging(boolean tlogging) {
1201    this.tlogging = tlogging;
1202  }
1203
1204  public UcumService getUcumService() {
1205    return ucumService;
1206  }
1207
1208  public void setUcumService(UcumService ucumService) {
1209    this.ucumService = ucumService;
1210  }
1211
1212  @Override
1213  public List<StructureDefinition> getStructures() {
1214    List<StructureDefinition> res = new ArrayList<>();
1215    synchronized (lock) { // tricky, because you need to lock this as well, but it's really not in use yet
1216      res.addAll(structures.values());
1217    }
1218    return res;
1219  }
1220
1221  public String getLinkForUrl(String corePath, String url) {
1222    for (CodeSystem r : codeSystems.values())
1223      if (url.equals(r.getUrl()))
1224        return r.getUserString("path");
1225
1226    for (ValueSet r : valueSets.values())
1227      if (url.equals(r.getUrl()))
1228        return r.getUserString("path");
1229
1230    for (ConceptMap r : maps.values())
1231      if (url.equals(r.getUrl()))
1232        return r.getUserString("path");
1233
1234    for (StructureMap r : transforms.values())
1235      if (url.equals(r.getUrl()))
1236        return r.getUserString("path");
1237
1238    for (StructureDefinition r : structures.values())
1239      if (url.equals(r.getUrl()))
1240        return r.getUserString("path");
1241
1242    for (ImplementationGuide r : guides.values())
1243      if (url.equals(r.getUrl()))
1244        return r.getUserString("path");
1245
1246    for (CapabilityStatement r : capstmts.values())
1247      if (url.equals(r.getUrl()))
1248        return r.getUserString("path");
1249
1250    for (SearchParameter r : searchParameters.values())
1251      if (url.equals(r.getUrl()))
1252        return r.getUserString("path");
1253
1254    for (Questionnaire r : questionnaires.values())
1255      if (url.equals(r.getUrl()))
1256        return r.getUserString("path");
1257
1258    for (OperationDefinition r : operations.values())
1259      if (url.equals(r.getUrl()))
1260        return r.getUserString("path");
1261
1262    for (PlanDefinition r : plans.values())
1263      if (url.equals(r.getUrl()))
1264        return r.getUserString("path");
1265
1266    if (url.equals("http://loinc.org"))
1267      return corePath + "loinc.html";
1268    if (url.equals("http://unitsofmeasure.org"))
1269      return corePath + "ucum.html";
1270    if (url.equals("http://snomed.info/sct"))
1271      return corePath + "snomed.html";
1272    return null;
1273  }
1274
1275  @SuppressWarnings("unchecked")
1276  public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_) {
1277
1278    List<T> res = new ArrayList<>();
1279
1280    synchronized (lock) {
1281
1282      if (class_ == Resource.class || class_ == DomainResource.class || class_ == null) {
1283        res.addAll((Collection<T>) structures.values());
1284        res.addAll((Collection<T>) guides.values());
1285        res.addAll((Collection<T>) capstmts.values());
1286        res.addAll((Collection<T>) valueSets.values());
1287        res.addAll((Collection<T>) codeSystems.values());
1288        res.addAll((Collection<T>) operations.values());
1289        res.addAll((Collection<T>) searchParameters.values());
1290        res.addAll((Collection<T>) plans.values());
1291        res.addAll((Collection<T>) maps.values());
1292        res.addAll((Collection<T>) transforms.values());
1293        res.addAll((Collection<T>) questionnaires.values());
1294      } else if (class_ == ImplementationGuide.class) {
1295        res.addAll((Collection<T>) guides.values());
1296      } else if (class_ == CapabilityStatement.class) {
1297        res.addAll((Collection<T>) capstmts.values());
1298      } else if (class_ == StructureDefinition.class) {
1299        res.addAll((Collection<T>) structures.values());
1300      } else if (class_ == StructureMap.class) {
1301        res.addAll((Collection<T>) transforms.values());
1302      } else if (class_ == ValueSet.class) {
1303        res.addAll((Collection<T>) valueSets.values());
1304      } else if (class_ == CodeSystem.class) {
1305        res.addAll((Collection<T>) codeSystems.values());
1306      } else if (class_ == ConceptMap.class) {
1307        res.addAll((Collection<T>) maps.values());
1308      } else if (class_ == PlanDefinition.class) {
1309        res.addAll((Collection<T>) plans.values());
1310      } else if (class_ == OperationDefinition.class) {
1311        res.addAll((Collection<T>) operations.values());
1312      } else if (class_ == Questionnaire.class) {
1313        res.addAll((Collection<T>) questionnaires.values());
1314      } else if (class_ == SearchParameter.class) {
1315        res.addAll((Collection<T>) searchParameters.values());
1316      }
1317    }
1318    return res;
1319  }
1320
1321}