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