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