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}