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