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