001package org.hl7.fhir.r5.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 033 034import java.io.File; 035import java.io.FileNotFoundException; 036import java.io.FileOutputStream; 037import java.io.IOException; 038import java.io.OutputStreamWriter; 039import java.util.*; 040 041import lombok.Getter; 042import lombok.Setter; 043import lombok.experimental.Accessors; 044import org.apache.commons.lang3.StringUtils; 045import org.hl7.fhir.exceptions.FHIRException; 046import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 047import org.hl7.fhir.r5.formats.IParser.OutputStyle; 048import org.hl7.fhir.r5.formats.JsonParser; 049import org.hl7.fhir.r5.model.*; 050import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 051import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 052import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 053import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 054import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 055import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 056import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 057import org.hl7.fhir.utilities.TextFile; 058import org.hl7.fhir.utilities.Utilities; 059import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 060import org.hl7.fhir.utilities.validation.ValidationOptions; 061 062import com.google.gson.JsonElement; 063import com.google.gson.JsonObject; 064import com.google.gson.JsonPrimitive; 065 066/** 067 * This implements a two level cache. 068 * - a temporary cache for remembering previous local operations 069 * - a persistent cache for remembering tx server operations 070 * 071 * the cache is a series of pairs: a map, and a list. the map is the loaded cache, the list is the persistent cache, carefully maintained in order for version control consistency 072 * 073 * @author graha 074 * 075 */ 076public class TerminologyCache { 077 078 public static final boolean TRANSIENT = false; 079 public static final boolean PERMANENT = true; 080 private static final String NAME_FOR_NO_SYSTEM = "all-systems"; 081 private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------"; 082 private static final String BREAK = "####"; 083 private static final String CACHE_FILE_EXTENSION = ".cache"; 084 private static final String CAPABILITY_STATEMENT_TITLE = ".capabilityStatement"; 085 private static final String TERMINOLOGY_CAPABILITIES_TITLE = ".terminologyCapabilities"; 086 087 088 private SystemNameKeyGenerator systemNameKeyGenerator = new SystemNameKeyGenerator(); 089 090 public class CacheToken { 091 @Getter 092 private String name; 093 private String key; 094 @Getter 095 private String request; 096 @Accessors(fluent = true) 097 @Getter 098 private boolean hasVersion; 099 100 public void setName(String n) { 101 String systemName = getSystemNameKeyGenerator().getNameForSystem(n); 102 if (name == null) 103 name = systemName; 104 else if (!systemName.equals(name)) 105 name = NAME_FOR_NO_SYSTEM; 106 } 107 } 108 109 protected SystemNameKeyGenerator getSystemNameKeyGenerator() { 110 return systemNameKeyGenerator; 111 } 112 public class SystemNameKeyGenerator { 113 public static final String SNOMED_SCT_CODESYSTEM_URL = "http://snomed.info/sct"; 114 public static final String RXNORM_CODESYSTEM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm"; 115 public static final String LOINC_CODESYSTEM_URL = "http://loinc.org"; 116 public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org"; 117 118 public static final String HL7_TERMINOLOGY_CODESYSTEM_BASE_URL = "http://terminology.hl7.org/CodeSystem/"; 119 public static final String HL7_SID_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/sid/"; 120 public static final String HL7_FHIR_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/"; 121 122 public static final String ISO_CODESYSTEM_URN = "urn:iso:std:iso:"; 123 public static final String LANG_CODESYSTEM_URN = "urn:ietf:bcp:47"; 124 public static final String MIMETYPES_CODESYSTEM_URN = "urn:ietf:bcp:13"; 125 126 public static final String _11073_CODESYSTEM_URN = "urn:iso:std:iso:11073:10101"; 127 public static final String DICOM_CODESYSTEM_URL = "http://dicom.nema.org/resources/ontology/DCM"; 128 129 public String getNameForSystem(String system) { 130 final int lastPipe = system.lastIndexOf('|'); 131 final String systemBaseName = lastPipe == -1 ? system : system.substring(0,lastPipe); 132 final String systemVersion = lastPipe == -1 ? null : system.substring(lastPipe + 1); 133 134 if (systemBaseName.equals(SNOMED_SCT_CODESYSTEM_URL)) 135 return getVersionedSystem("snomed", systemVersion); 136 if (systemBaseName.equals(RXNORM_CODESYSTEM_URL)) 137 return getVersionedSystem("rxnorm", systemVersion); 138 if (systemBaseName.equals(LOINC_CODESYSTEM_URL)) 139 return getVersionedSystem("loinc", systemVersion); 140 if (systemBaseName.equals(UCUM_CODESYSTEM_URL)) 141 return getVersionedSystem("ucum", systemVersion); 142 if (systemBaseName.startsWith(HL7_SID_CODESYSTEM_BASE_URL)) 143 return getVersionedSystem(normalizeBaseURL(HL7_SID_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); 144 if (systemBaseName.equals(_11073_CODESYSTEM_URN)) 145 return getVersionedSystem("11073", systemVersion); 146 if (systemBaseName.startsWith(ISO_CODESYSTEM_URN)) 147 return getVersionedSystem("iso"+systemBaseName.substring(ISO_CODESYSTEM_URN.length()).replace(":", ""), systemVersion); 148 if (systemBaseName.startsWith(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL)) 149 return getVersionedSystem(normalizeBaseURL(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); 150 if (systemBaseName.startsWith(HL7_FHIR_CODESYSTEM_BASE_URL)) 151 return getVersionedSystem(normalizeBaseURL(HL7_FHIR_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); 152 if (systemBaseName.equals(LANG_CODESYSTEM_URN)) 153 return getVersionedSystem("lang", systemVersion); 154 if (systemBaseName.equals(MIMETYPES_CODESYSTEM_URN)) 155 return getVersionedSystem("mimetypes", systemVersion); 156 if (systemBaseName.equals(DICOM_CODESYSTEM_URL)) 157 return getVersionedSystem("dicom", systemVersion); 158 return getVersionedSystem(systemBaseName.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"), systemVersion); 159 } 160 161 public String normalizeBaseURL(String baseUrl, String fullUrl) { 162 return fullUrl.substring(baseUrl.length()).replace("/", ""); 163 } 164 165 public String getVersionedSystem(String baseSystem, String version) { 166 if (version != null) { 167 return baseSystem + "_" + version; 168 } 169 return baseSystem; 170 } 171 } 172 173 174 private class CacheEntry { 175 private String request; 176 private boolean persistent; 177 private ValidationResult v; 178 private ValueSetExpansionOutcome e; 179 } 180 181 private class NamedCache { 182 private String name; 183 private List<CacheEntry> list = new ArrayList<CacheEntry>(); // persistent entries 184 private Map<String, CacheEntry> map = new HashMap<String, CacheEntry>(); 185 } 186 187 188 private Object lock; 189 private String folder; 190 @Getter private int requestCount; 191 @Getter private int hitCount; 192 @Getter private int networkCount; 193 private CapabilityStatement capabilityStatementCache = null; 194 private TerminologyCapabilities terminologyCapabilitiesCache = null; 195 private Map<String, NamedCache> caches = new HashMap<String, NamedCache>(); 196 @Getter @Setter private static boolean noCaching; 197 198 @Getter @Setter private static boolean cacheErrors; 199 200 201 // use lock from the context 202 public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException { 203 super(); 204 this.lock = lock; 205 this.folder = folder; 206 requestCount = 0; 207 hitCount = 0; 208 networkCount = 0; 209 210 if (folder != null) { 211 load(); 212 } 213 } 214 215 public boolean hasCapabilityStatement() { 216 return capabilityStatementCache != null; 217 } 218 219 public CapabilityStatement getCapabilityStatement() { 220 return capabilityStatementCache; 221 } 222 223 public void cacheCapabilityStatement(CapabilityStatement capabilityStatement) { 224 if (noCaching) { 225 return; 226 } 227 this.capabilityStatementCache = capabilityStatement; 228 save(capabilityStatementCache, CAPABILITY_STATEMENT_TITLE); 229 } 230 231 232 public boolean hasTerminologyCapabilities() { 233 return terminologyCapabilitiesCache != null; 234 } 235 236 public TerminologyCapabilities getTerminologyCapabilities() { 237 return terminologyCapabilitiesCache; 238 } 239 240 public void cacheTerminologyCapabilities(TerminologyCapabilities terminologyCapabilities) { 241 if (noCaching) { 242 return; 243 } 244 this.terminologyCapabilitiesCache = terminologyCapabilities; 245 save(terminologyCapabilitiesCache, TERMINOLOGY_CAPABILITIES_TITLE); 246 } 247 248 249 public void clear() { 250 caches.clear(); 251 } 252 253 public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) { 254 CacheToken ct = new CacheToken(); 255 if (code.hasSystem()) { 256 ct.setName(code.getSystem()); 257 ct.hasVersion = code.hasVersion(); 258 } 259 else 260 ct.name = NAME_FOR_NO_SYSTEM; 261 nameCacheToken(vs, ct); 262 JsonParser json = new JsonParser(); 263 json.setOutputStyle(OutputStyle.PRETTY); 264 if (vs != null && vs.hasUrl() && vs.hasVersion()) { 265 try { 266 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl()) 267 +"\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\""+(options == null ? "" : ", "+options.toJson())+"}\r\n"; 268 } catch (IOException e) { 269 throw new Error(e); 270 } 271 } else { 272 ValueSet vsc = getVSEssense(vs); 273 try { 274 ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsc == null ? "null" : extracted(json, vsc))+(options == null ? "" : ", "+options.toJson())+"}"; 275 } catch (IOException e) { 276 throw new Error(e); 277 } 278 } 279 ct.key = String.valueOf(hashJson(ct.request)); 280 return ct; 281 } 282 283 public String extracted(JsonParser json, ValueSet vsc) throws IOException { 284 String s = null; 285 if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) { 286 s = vsc.getUrl(); 287 } else { 288 s = json.composeString(vsc); 289 } 290 return s; 291 } 292 293 public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs) { 294 CacheToken ct = new CacheToken(); 295 for (Coding c : code.getCoding()) { 296 if (c.hasSystem()) { 297 ct.setName(c.getSystem()); 298 ct.hasVersion = c.hasVersion(); 299 } 300 } 301 nameCacheToken(vs, ct); 302 JsonParser json = new JsonParser(); 303 json.setOutputStyle(OutputStyle.PRETTY); 304 if (vs != null && vs.hasUrl() && vs.hasVersion()) { 305 try { 306 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())+ 307 "\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\""+(options == null ? "" : ", "+options.toJson())+"+}\r\n"; 308 } catch (IOException e) { 309 throw new Error(e); 310 } 311 } else { 312 ValueSet vsc = getVSEssense(vs); 313 try { 314 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"valueSet\" :"+extracted(json, vsc)+(options == null ? "" : ", "+options.toJson())+"}"; 315 } catch (IOException e) { 316 throw new Error(e); 317 } 318 } 319 ct.key = String.valueOf(hashJson(ct.request)); 320 return ct; 321 } 322 323 public ValueSet getVSEssense(ValueSet vs) { 324 if (vs == null) 325 return null; 326 ValueSet vsc = new ValueSet(); 327 vsc.setCompose(vs.getCompose()); 328 if (vs.hasExpansion()) { 329 vsc.getExpansion().getParameter().addAll(vs.getExpansion().getParameter()); 330 vsc.getExpansion().getContains().addAll(vs.getExpansion().getContains()); 331 } 332 return vsc; 333 } 334 335 public CacheToken generateExpandToken(ValueSet vs, boolean hierarchical) { 336 CacheToken ct = new CacheToken(); 337 nameCacheToken(vs, ct); 338 if (vs.hasUrl() && vs.hasVersion()) { 339 ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())+"\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\"}\r\n"; 340 } else { 341 ValueSet vsc = getVSEssense(vs); 342 JsonParser json = new JsonParser(); 343 json.setOutputStyle(OutputStyle.PRETTY); 344 try { 345 ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"valueSet\" :"+extracted(json, vsc)+"}\r\n"; 346 } catch (IOException e) { 347 throw new Error(e); 348 } 349 } 350 ct.key = String.valueOf(hashJson(ct.request)); 351 return ct; 352 } 353 354 public void nameCacheToken(ValueSet vs, CacheToken ct) { 355 if (vs != null) { 356 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 357 if (inc.hasSystem()) { 358 ct.setName(inc.getSystem()); 359 ct.hasVersion = inc.hasVersion(); 360 } 361 } 362 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 363 if (inc.hasSystem()) { 364 ct.setName(inc.getSystem()); 365 ct.hasVersion = inc.hasVersion(); 366 } 367 } 368 for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains()) { 369 if (inc.hasSystem()) { 370 ct.setName(inc.getSystem()); 371 ct.hasVersion = inc.hasVersion(); 372 } 373 } 374 } 375 } 376 377 private String normalizeSystemPath(String path) { 378 return path.replace("/", "").replace('|','X'); 379 } 380 381 382 383 public NamedCache getNamedCache(CacheToken cacheToken) { 384 385 final String cacheName = cacheToken.name == null ? "null" : cacheToken.name; 386 387 NamedCache nc = caches.get(cacheName); 388 389 if (nc == null) { 390 nc = new NamedCache(); 391 nc.name = cacheName; 392 caches.put(nc.name, nc); 393 } 394 return nc; 395 } 396 397 public ValueSetExpansionOutcome getExpansion(CacheToken cacheToken) { 398 synchronized (lock) { 399 NamedCache nc = getNamedCache(cacheToken); 400 CacheEntry e = nc.map.get(cacheToken.key); 401 if (e == null) 402 return null; 403 else 404 return e.e; 405 } 406 } 407 408 public void cacheExpansion(CacheToken cacheToken, ValueSetExpansionOutcome res, boolean persistent) { 409 synchronized (lock) { 410 NamedCache nc = getNamedCache(cacheToken); 411 CacheEntry e = new CacheEntry(); 412 e.request = cacheToken.request; 413 e.persistent = persistent; 414 e.e = res; 415 store(cacheToken, persistent, nc, e); 416 } 417 } 418 419 public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) { 420 if (noCaching) { 421 return; 422 } 423 424 if ( !cacheErrors && 425 ( e.v!= null 426 && e.v.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED 427 && !cacheToken.hasVersion)) { 428 return; 429 } 430 431 boolean n = nc.map.containsKey(cacheToken.key); 432 nc.map.put(cacheToken.key, e); 433 if (persistent) { 434 if (n) { 435 for (int i = nc.list.size()- 1; i>= 0; i--) { 436 if (nc.list.get(i).request.equals(e.request)) { 437 nc.list.remove(i); 438 } 439 } 440 } 441 nc.list.add(e); 442 save(nc); 443 } 444 } 445 446 public ValidationResult getValidation(CacheToken cacheToken) { 447 if (cacheToken.key == null) { 448 return null; 449 } 450 synchronized (lock) { 451 requestCount++; 452 NamedCache nc = getNamedCache(cacheToken); 453 CacheEntry e = nc.map.get(cacheToken.key); 454 if (e == null) { 455 networkCount++; 456 return null; 457 } else { 458 hitCount++; 459 return e.v; 460 } 461 } 462 } 463 464 public void cacheValidation(CacheToken cacheToken, ValidationResult res, boolean persistent) { 465 if (cacheToken.key != null) { 466 synchronized (lock) { 467 NamedCache nc = getNamedCache(cacheToken); 468 CacheEntry e = new CacheEntry(); 469 e.request = cacheToken.request; 470 e.persistent = persistent; 471 e.v = res; 472 store(cacheToken, persistent, nc, e); 473 } 474 } 475 } 476 477 478 // persistence 479 480 public void save() { 481 482 } 483 484 private <K extends Resource> void save(K resource, String title) { 485 if (folder == null) 486 return; 487 488 try { 489 OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, title + CACHE_FILE_EXTENSION)), "UTF-8"); 490 491 JsonParser json = new JsonParser(); 492 json.setOutputStyle(OutputStyle.PRETTY); 493 494 sw.write(json.composeString(resource).trim()); 495 sw.close(); 496 } catch (Exception e) { 497 System.out.println("error saving capability statement "+e.getMessage()); 498 } 499 } 500 501 private void save(NamedCache nc) { 502 if (folder == null) 503 return; 504 505 try { 506 OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, nc.name+CACHE_FILE_EXTENSION)), "UTF-8"); 507 sw.write(ENTRY_MARKER+"\r\n"); 508 JsonParser json = new JsonParser(); 509 json.setOutputStyle(OutputStyle.PRETTY); 510 for (CacheEntry ce : nc.list) { 511 sw.write(ce.request.trim()); 512 sw.write(BREAK+"\r\n"); 513 if (ce.e != null) { 514 sw.write("e: {\r\n"); 515 if (ce.e.getValueset() != null) 516 sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n"); 517 sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n"); 518 } else { 519 sw.write("v: {\r\n"); 520 boolean first = true; 521 if (ce.v.getDisplay() != null) { 522 if (first) first = false; else sw.write(",\r\n"); 523 sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\""); 524 } 525 if (ce.v.getCode() != null) { 526 if (first) first = false; else sw.write(",\r\n"); 527 sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\""); 528 } 529 if (ce.v.getSystem() != null) { 530 if (first) first = false; else sw.write(",\r\n"); 531 sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\""); 532 } 533 if (ce.v.getSeverity() != null) { 534 if (first) first = false; else sw.write(",\r\n"); 535 sw.write(" \"severity\" : "+"\""+ce.v.getSeverity().toCode().trim()+"\""+""); 536 } 537 if (ce.v.getMessage() != null) { 538 if (first) first = false; else sw.write(",\r\n"); 539 sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\""); 540 } 541 if (ce.v.getErrorClass() != null) { 542 if (first) first = false; else sw.write(",\r\n"); 543 sw.write(" \"class\" : \""+Utilities.escapeJson(ce.v.getErrorClass().toString())+"\""); 544 } 545 if (ce.v.getDefinition() != null) { 546 if (first) first = false; else sw.write(",\r\n"); 547 sw.write(" \"definition\" : \""+Utilities.escapeJson(ce.v.getDefinition()).trim()+"\""); 548 } 549 sw.write("\r\n}\r\n"); 550 } 551 sw.write(ENTRY_MARKER+"\r\n"); 552 } 553 sw.close(); 554 } catch (Exception e) { 555 System.out.println("error saving "+nc.name+": "+e.getMessage()); 556 } 557 } 558 559 private boolean isCapabilityCache(String fn) { 560 if (fn == null) { 561 return false; 562 } 563 return fn.startsWith(CAPABILITY_STATEMENT_TITLE) || fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE); 564 } 565 566 private void loadCapabilityCache(String fn) { 567 try { 568 String src = TextFile.fileToString(Utilities.path(folder, fn)); 569 570 JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(src); 571 Resource resource = new JsonParser().parse(o); 572 573 if (fn.startsWith(CAPABILITY_STATEMENT_TITLE)) { 574 this.capabilityStatementCache = (CapabilityStatement) resource; 575 } else if (fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE)) { 576 this.terminologyCapabilitiesCache = (TerminologyCapabilities) resource; 577 } 578 } catch (Exception e) { 579 e.printStackTrace(); 580 throw new FHIRException("Error loading " + fn + ": " + e.getMessage(), e); 581 } 582 } 583 584 585 586 private CacheEntry getCacheEntry(String request, String resultString) throws IOException { 587 CacheEntry ce = new CacheEntry(); 588 ce.persistent = true; 589 ce.request = request; 590 boolean e = resultString.charAt(0) == 'e'; 591 resultString = resultString.substring(3); 592 JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(resultString); 593 String error = loadJS(o.get("error")); 594 if (e) { 595 if (o.has("valueSet")) 596 ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN); 597 else 598 ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN); 599 } else { 600 String t = loadJS(o.get("severity")); 601 IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t); 602 String display = loadJS(o.get("display")); 603 String code = loadJS(o.get("code")); 604 String system = loadJS(o.get("system")); 605 String definition = loadJS(o.get("definition")); 606 t = loadJS(o.get("class")); 607 TerminologyServiceErrorClass errorClass = t == null ? null : TerminologyServiceErrorClass.valueOf(t) ; 608 ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setDefinition(definition).setCode(code)).setErrorClass(errorClass); 609 } 610 return ce; 611 } 612 613 private void loadNamedCache(String fn) { 614 int c = 0; 615 try { 616 String src = TextFile.fileToString(Utilities.path(folder, fn)); 617 String title = fn.substring(0, fn.lastIndexOf(".")); 618 619 NamedCache nc = new NamedCache(); 620 nc.name = title; 621 622 if (src.startsWith("?")) 623 src = src.substring(1); 624 int i = src.indexOf(ENTRY_MARKER); 625 while (i > -1) { 626 c++; 627 String s = src.substring(0, i); 628 src = src.substring(i + ENTRY_MARKER.length() + 1); 629 i = src.indexOf(ENTRY_MARKER); 630 if (!Utilities.noString(s)) { 631 int j = s.indexOf(BREAK); 632 String request = s.substring(0, j); 633 String p = s.substring(j + BREAK.length() + 1).trim(); 634 635 CacheEntry cacheEntry = getCacheEntry(request, p); 636 637 nc.map.put(String.valueOf(hashJson(cacheEntry.request)), cacheEntry); 638 nc.list.add(cacheEntry); 639 } 640 caches.put(nc.name, nc); 641 } 642 } catch (Exception e) { 643 System.out.println("Error loading "+fn+": "+e.getMessage()+" entry "+c+" - ignoring it"); 644 e.printStackTrace(); 645 } 646 } 647 648 private void load() throws FHIRException { 649 for (String fn : new File(folder).list()) { 650 if (fn.endsWith(CACHE_FILE_EXTENSION) && !fn.equals("validation" + CACHE_FILE_EXTENSION)) { 651 try { 652 if (isCapabilityCache(fn)) { 653 loadCapabilityCache(fn); 654 } else { 655 loadNamedCache(fn); 656 } 657 } catch (FHIRException e) { 658 throw e; 659 } 660 } 661 } 662 } 663 664 private String loadJS(JsonElement e) { 665 if (e == null) 666 return null; 667 if (!(e instanceof JsonPrimitive)) 668 return null; 669 String s = e.getAsString(); 670 if ("".equals(s)) 671 return null; 672 return s; 673 } 674 675 protected String hashJson(String s) { 676 s = StringUtils.remove(s, ' '); 677 s = StringUtils.remove(s, '\n'); 678 s = StringUtils.remove(s, '\r'); 679 return String.valueOf(s.hashCode()); 680 } 681 682 // management 683 684 public String summary(ValueSet vs) { 685 if (vs == null) 686 return "null"; 687 688 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 689 for (ConceptSetComponent cc : vs.getCompose().getInclude()) 690 b.append("Include "+getIncSummary(cc)); 691 for (ConceptSetComponent cc : vs.getCompose().getExclude()) 692 b.append("Exclude "+getIncSummary(cc)); 693 return b.toString(); 694 } 695 696 private String getIncSummary(ConceptSetComponent cc) { 697 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 698 for (UriType vs : cc.getValueSet()) 699 b.append(vs.asStringValue()); 700 String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : ""; 701 String system = cc.getSystem(); 702 if (cc.hasConcept()) 703 return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd; 704 if (cc.hasFilter()) { 705 String s = ""; 706 for (ConceptSetFilterComponent f : cc.getFilter()) { 707 if (!Utilities.noString(s)) 708 s = s + " & "; 709 s = s + f.getProperty()+" "+(f.hasOp() ? f.getOp().toCode() : "?")+" "+f.getValue(); 710 } 711 return "from "+system+" where "+s+vsd; 712 } 713 return "All codes from "+system+vsd; 714 } 715 716 public String summary(Coding code) { 717 return code.getSystem()+"#"+code.getCode()+": \""+code.getDisplay()+"\""; 718 } 719 720 public String summary(CodeableConcept code) { 721 StringBuilder b = new StringBuilder(); 722 b.append("{"); 723 boolean first = true; 724 for (Coding c : code.getCoding()) { 725 if (first) first = false; else b.append(","); 726 b.append(summary(c)); 727 } 728 b.append("}: \""); 729 b.append(code.getText()); 730 b.append("\""); 731 return b.toString(); 732 } 733 734 public void removeCS(String url) { 735 synchronized (lock) { 736 String name = getSystemNameKeyGenerator().getNameForSystem(url); 737 if (caches.containsKey(name)) { 738 caches.remove(name); 739 } 740 } 741 } 742 743 public String getFolder() { 744 return folder; 745 } 746 747 748}