
001package org.hl7.fhir.r5.terminologies.utilities; 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.IOException; 037import java.io.OutputStreamWriter; 038import java.util.*; 039 040import lombok.Getter; 041import lombok.Setter; 042import lombok.experimental.Accessors; 043import org.hl7.fhir.exceptions.FHIRException; 044import org.hl7.fhir.r5.formats.IParser.OutputStyle; 045import org.hl7.fhir.r5.formats.JsonParser; 046import org.hl7.fhir.r5.model.*; 047import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 048import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 049import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 050import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 051import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 052import org.hl7.fhir.r5.utils.UserDataNames; 053import org.hl7.fhir.utilities.*; 054import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 055import org.hl7.fhir.utilities.json.model.JsonNull; 056import org.hl7.fhir.utilities.json.model.JsonProperty; 057import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 058import org.hl7.fhir.utilities.validation.ValidationOptions; 059 060import com.google.gson.JsonElement; 061import com.google.gson.JsonObject; 062import com.google.gson.JsonPrimitive; 063 064/** 065 * This implements a two level cache. 066 * - a temporary cache for remembering previous local operations 067 * - a persistent cache for remembering tx server operations 068 * 069 * 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 070 * 071 * @author graha 072 * 073 */ 074@MarkedToMoveToAdjunctPackage 075public class TerminologyCache { 076 077 public static class SourcedCodeSystem { 078 private String server; 079 private CodeSystem cs; 080 081 public SourcedCodeSystem(String server, CodeSystem cs) { 082 super(); 083 this.server = server; 084 this.cs = cs; 085 } 086 public String getServer() { 087 return server; 088 } 089 public CodeSystem getCs() { 090 return cs; 091 } 092 } 093 094 095 public static class SourcedCodeSystemEntry { 096 private String server; 097 private String filename; 098 099 public SourcedCodeSystemEntry(String server, String filename) { 100 super(); 101 this.server = server; 102 this.filename = filename; 103 } 104 public String getServer() { 105 return server; 106 } 107 public String getFilename() { 108 return filename; 109 } 110 } 111 112 113 public static class SourcedValueSet { 114 private String server; 115 private ValueSet vs; 116 117 public SourcedValueSet(String server, ValueSet vs) { 118 super(); 119 this.server = server; 120 this.vs = vs; 121 } 122 public String getServer() { 123 return server; 124 } 125 public ValueSet getVs() { 126 return vs; 127 } 128 } 129 130 public static class SourcedValueSetEntry { 131 private String server; 132 private String filename; 133 134 public SourcedValueSetEntry(String server, String filename) { 135 super(); 136 this.server = server; 137 this.filename = filename; 138 } 139 public String getServer() { 140 return server; 141 } 142 public String getFilename() { 143 return filename; 144 } 145 } 146 147 public static final boolean TRANSIENT = false; 148 public static final boolean PERMANENT = true; 149 private static final String NAME_FOR_NO_SYSTEM = "all-systems"; 150 private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------"; 151 private static final String BREAK = "####"; 152 private static final String CACHE_FILE_EXTENSION = ".cache"; 153 private static final String CAPABILITY_STATEMENT_TITLE = ".capabilityStatement"; 154 private static final String TERMINOLOGY_CAPABILITIES_TITLE = ".terminologyCapabilities"; 155 private static final String FIXED_CACHE_VERSION = "4"; // last change: change the way tx.fhir.org handles expansions 156 157 158 private SystemNameKeyGenerator systemNameKeyGenerator = new SystemNameKeyGenerator(); 159 160 public class CacheToken { 161 @Getter 162 private String name; 163 private String key; 164 @Getter 165 private String request; 166 @Accessors(fluent = true) 167 @Getter 168 private boolean hasVersion; 169 170 public void setName(String n) { 171 String systemName = getSystemNameKeyGenerator().getNameForSystem(n); 172 if (name == null) 173 name = systemName; 174 else if (!systemName.equals(name)) 175 name = NAME_FOR_NO_SYSTEM; 176 } 177 } 178 179 public static class SubsumesResult { 180 181 private Boolean result; 182 183 protected SubsumesResult(Boolean result) { 184 super(); 185 this.result = result; 186 } 187 188 public Boolean getResult() { 189 return result; 190 } 191 192 } 193 194 protected SystemNameKeyGenerator getSystemNameKeyGenerator() { 195 return systemNameKeyGenerator; 196 } 197 public class SystemNameKeyGenerator { 198 public static final String SNOMED_SCT_CODESYSTEM_URL = "http://snomed.info/sct"; 199 public static final String RXNORM_CODESYSTEM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm"; 200 public static final String LOINC_CODESYSTEM_URL = "http://loinc.org"; 201 public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org"; 202 203 public static final String HL7_TERMINOLOGY_CODESYSTEM_BASE_URL = "http://terminology.hl7.org/CodeSystem/"; 204 public static final String HL7_SID_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/sid/"; 205 public static final String HL7_FHIR_CODESYSTEM_BASE_URL = "http://hl7.org/fhir/"; 206 207 public static final String ISO_CODESYSTEM_URN = "urn:iso:std:iso:"; 208 public static final String LANG_CODESYSTEM_URN = "urn:ietf:bcp:47"; 209 public static final String MIMETYPES_CODESYSTEM_URN = "urn:ietf:bcp:13"; 210 211 public static final String _11073_CODESYSTEM_URN = "urn:iso:std:iso:11073:10101"; 212 public static final String DICOM_CODESYSTEM_URL = "http://dicom.nema.org/resources/ontology/DCM"; 213 214 public String getNameForSystem(String system) { 215 final int lastPipe = system.lastIndexOf('|'); 216 final String systemBaseName = lastPipe == -1 ? system : system.substring(0,lastPipe); 217 final String systemVersion = lastPipe == -1 ? null : system.substring(lastPipe + 1); 218 219 if (systemBaseName.equals(SNOMED_SCT_CODESYSTEM_URL)) 220 return getVersionedSystem("snomed", systemVersion); 221 if (systemBaseName.equals(RXNORM_CODESYSTEM_URL)) 222 return getVersionedSystem("rxnorm", systemVersion); 223 if (systemBaseName.equals(LOINC_CODESYSTEM_URL)) 224 return getVersionedSystem("loinc", systemVersion); 225 if (systemBaseName.equals(UCUM_CODESYSTEM_URL)) 226 return getVersionedSystem("ucum", systemVersion); 227 if (systemBaseName.startsWith(HL7_SID_CODESYSTEM_BASE_URL)) 228 return getVersionedSystem(normalizeBaseURL(HL7_SID_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); 229 if (systemBaseName.equals(_11073_CODESYSTEM_URN)) 230 return getVersionedSystem("11073", systemVersion); 231 if (systemBaseName.startsWith(ISO_CODESYSTEM_URN)) 232 return getVersionedSystem("iso"+systemBaseName.substring(ISO_CODESYSTEM_URN.length()).replace(":", ""), systemVersion); 233 if (systemBaseName.startsWith(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL)) 234 return getVersionedSystem(normalizeBaseURL(HL7_TERMINOLOGY_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); 235 if (systemBaseName.startsWith(HL7_FHIR_CODESYSTEM_BASE_URL)) 236 return getVersionedSystem(normalizeBaseURL(HL7_FHIR_CODESYSTEM_BASE_URL, systemBaseName), systemVersion); 237 if (systemBaseName.equals(LANG_CODESYSTEM_URN)) 238 return getVersionedSystem("lang", systemVersion); 239 if (systemBaseName.equals(MIMETYPES_CODESYSTEM_URN)) 240 return getVersionedSystem("mimetypes", systemVersion); 241 if (systemBaseName.equals(DICOM_CODESYSTEM_URL)) 242 return getVersionedSystem("dicom", systemVersion); 243 return getVersionedSystem(systemBaseName.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X"), systemVersion); 244 } 245 246 public String normalizeBaseURL(String baseUrl, String fullUrl) { 247 return fullUrl.substring(baseUrl.length()).replace("/", ""); 248 } 249 250 public String getVersionedSystem(String baseSystem, String version) { 251 if (version != null) { 252 return baseSystem + "_" + version; 253 } 254 return baseSystem; 255 } 256 } 257 258 259 private class CacheEntry { 260 private String request; 261 private boolean persistent; 262 private ValidationResult v; 263 private ValueSetExpansionOutcome e; 264 private SubsumesResult s; 265 } 266 267 private class NamedCache { 268 private String name; 269 private List<CacheEntry> list = new ArrayList<CacheEntry>(); // persistent entries 270 private Map<String, CacheEntry> map = new HashMap<String, CacheEntry>(); 271 } 272 273 274 private Object lock; 275 private String folder; 276 @Getter private int requestCount; 277 @Getter private int hitCount; 278 @Getter private int networkCount; 279 private Map<String, CapabilityStatement> capabilityStatementCache = new HashMap<>(); 280 private Map<String, TerminologyCapabilities> terminologyCapabilitiesCache = new HashMap<>(); 281 private Map<String, NamedCache> caches = new HashMap<String, NamedCache>(); 282 private Map<String, SourcedValueSetEntry> vsCache = new HashMap<>(); 283 private Map<String, SourcedCodeSystemEntry> csCache = new HashMap<>(); 284 private Map<String, String> serverMap = new HashMap<>(); 285 @Getter @Setter private static boolean noCaching; 286 287 @Getter @Setter private static boolean cacheErrors; 288 289 290 // use lock from the context 291 public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException { 292 super(); 293 this.lock = lock; 294 if (folder == null) { 295 folder = Utilities.path("[tmp]", "default-tx-cache"); 296 } else if ("n/a".equals(folder)) { 297 // this is a weird way to do things but it maintains the legacy interface 298 folder = null; 299 } 300 this.folder = folder; 301 requestCount = 0; 302 hitCount = 0; 303 networkCount = 0; 304 305 if (folder != null) { 306 File f = ManagedFileAccess.file(folder); 307 if (!f.exists()) { 308 FileUtilities.createDirectory(folder); 309 } 310 if (!f.exists()) { 311 throw new IOException("Unable to create terminology cache at "+folder); 312 } 313 checkVersion(); 314 load(); 315 } 316 } 317 318 private void checkVersion() throws IOException { 319 File verFile = ManagedFileAccess.file(Utilities.path(folder, "version.ctl")); 320 if (verFile.exists()) { 321 String ver = FileUtilities.fileToString(verFile); 322 if (!ver.equals(FIXED_CACHE_VERSION)) { 323 System.out.println("Terminology Cache Version has changed from 1 to "+FIXED_CACHE_VERSION+", so clearing txCache"); 324 clear(); 325 } 326 FileUtilities.stringToFile(FIXED_CACHE_VERSION, verFile); 327 } else { 328 FileUtilities.stringToFile(FIXED_CACHE_VERSION, verFile); 329 } 330 } 331 332 public String getServerId(String address) throws IOException { 333 if (serverMap.containsKey(address)) { 334 return serverMap.get(address); 335 } 336 String id = address.replace("http://", "").replace("https://", "").replace("/", "."); 337 int i = 1; 338 while (serverMap.containsValue(id)) { 339 i++; 340 id = address.replace("https:", "").replace("https:", "").replace("/", ".")+i; 341 } 342 serverMap.put(address, id); 343 if (folder != null) { 344 IniFile ini = new IniFile(Utilities.path(folder, "servers.ini")); 345 ini.setStringProperty("servers", id, address, null); 346 ini.save(); 347 } 348 return id; 349 } 350 351 public void unload() { 352 // not useable after this is called 353 caches.clear(); 354 vsCache.clear(); 355 csCache.clear(); 356 } 357 358 public void clear() throws IOException { 359 if (folder != null) { 360 FileUtilities.clearDirectory(folder); 361 } 362 caches.clear(); 363 vsCache.clear(); 364 csCache.clear(); 365 } 366 367 public boolean hasCapabilityStatement(String address) { 368 return capabilityStatementCache.containsKey(address); 369 } 370 371 public CapabilityStatement getCapabilityStatement(String address) { 372 return capabilityStatementCache.get(address); 373 } 374 375 public void cacheCapabilityStatement(String address, CapabilityStatement capabilityStatement) throws IOException { 376 if (noCaching) { 377 return; 378 } 379 this.capabilityStatementCache.put(address, capabilityStatement); 380 save(capabilityStatement, CAPABILITY_STATEMENT_TITLE+"."+getServerId(address)); 381 } 382 383 384 public boolean hasTerminologyCapabilities(String address) { 385 return terminologyCapabilitiesCache.containsKey(address); 386 } 387 388 public TerminologyCapabilities getTerminologyCapabilities(String address) { 389 return terminologyCapabilitiesCache.get(address); 390 } 391 392 public void cacheTerminologyCapabilities(String address, TerminologyCapabilities terminologyCapabilities) throws IOException { 393 if (noCaching) { 394 return; 395 } 396 this.terminologyCapabilitiesCache.put(address, terminologyCapabilities); 397 save(terminologyCapabilities, TERMINOLOGY_CAPABILITIES_TITLE+"."+getServerId(address)); 398 } 399 400 401 public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs, Parameters expParameters) { 402 try { 403 CacheToken ct = new CacheToken(); 404 if (code.hasSystem()) { 405 ct.setName(code.getSystem()); 406 ct.hasVersion = code.hasVersion(); 407 } 408 else 409 ct.name = NAME_FOR_NO_SYSTEM; 410 nameCacheToken(vs, ct); 411 JsonParser json = new JsonParser(); 412 json.setOutputStyle(OutputStyle.PRETTY); 413 String expJS = expParameters == null ? "" : json.composeString(expParameters); 414 415 if (vs != null && vs.hasUrl() && vs.hasVersion()) { 416 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl()) 417 +"\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\""+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}\r\n"; 418 } else if (options.getVsAsUrl()) { 419 ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+extracted(json, vs)+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}"; 420 } else { 421 ValueSet vsc = getVSEssense(vs); 422 ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsc == null ? "null" : extracted(json, vsc))+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}"; 423 } 424 ct.key = String.valueOf(hashJson(ct.request)); 425 return ct; 426 } catch (IOException e) { 427 throw new Error(e); 428 } 429 } 430 431 public CacheToken generateValidationToken(ValidationOptions options, Coding code, String vsUrl, Parameters expParameters) { 432 try { 433 CacheToken ct = new CacheToken(); 434 if (code.hasSystem()) { 435 ct.setName(code.getSystem()); 436 ct.hasVersion = code.hasVersion(); 437 } else { 438 ct.name = NAME_FOR_NO_SYSTEM; 439 } 440 ct.setName(vsUrl); 441 JsonParser json = new JsonParser(); 442 json.setOutputStyle(OutputStyle.PRETTY); 443 String expJS = json.composeString(expParameters); 444 445 ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsUrl == null ? "null" : vsUrl)+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}"; 446 ct.key = String.valueOf(hashJson(ct.request)); 447 return ct; 448 } catch (IOException e) { 449 throw new Error(e); 450 } 451 } 452 453 public String extracted(JsonParser json, ValueSet vsc) throws IOException { 454 String s = null; 455 if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) { 456 s = vsc.getUrl(); 457 } else { 458 s = json.composeString(vsc); 459 } 460 return s; 461 } 462 463 public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs, Parameters expParameters) { 464 try { 465 CacheToken ct = new CacheToken(); 466 for (Coding c : code.getCoding()) { 467 if (c.hasSystem()) { 468 ct.setName(c.getSystem()); 469 ct.hasVersion = c.hasVersion(); 470 } 471 } 472 nameCacheToken(vs, ct); 473 JsonParser json = new JsonParser(); 474 json.setOutputStyle(OutputStyle.PRETTY); 475 String expJS = json.composeString(expParameters); 476 if (vs != null && vs.hasUrl() && vs.hasVersion()) { 477 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())+ 478 "\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\""+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}\r\n"; 479 } else if (vs == null) { 480 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}"; 481 } else { 482 ValueSet vsc = getVSEssense(vs); 483 ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"valueSet\" :"+extracted(json, vsc)+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}"; 484 } 485 ct.key = String.valueOf(hashJson(ct.request)); 486 return ct; 487 } catch (IOException e) { 488 throw new Error(e); 489 } 490 } 491 492 public ValueSet getVSEssense(ValueSet vs) { 493 if (vs == null) 494 return null; 495 ValueSet vsc = new ValueSet(); 496 vsc.setCompose(vs.getCompose()); 497 if (vs.hasExpansion()) { 498 vsc.getExpansion().getParameter().addAll(vs.getExpansion().getParameter()); 499 vsc.getExpansion().getContains().addAll(vs.getExpansion().getContains()); 500 } 501 return vsc; 502 } 503 504 public CacheToken generateExpandToken(ValueSet vs, boolean hierarchical) { 505 CacheToken ct = new CacheToken(); 506 nameCacheToken(vs, ct); 507 if (vs.hasUrl() && vs.hasVersion()) { 508 ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())+"\", \"version\": \""+Utilities.escapeJson(vs.getVersion())+"\"}\r\n"; 509 } else { 510 ValueSet vsc = getVSEssense(vs); 511 JsonParser json = new JsonParser(); 512 json.setOutputStyle(OutputStyle.PRETTY); 513 try { 514 ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"valueSet\" :"+extracted(json, vsc)+"}\r\n"; 515 } catch (IOException e) { 516 throw new Error(e); 517 } 518 } 519 ct.key = String.valueOf(hashJson(ct.request)); 520 return ct; 521 } 522 523 public CacheToken generateExpandToken(String url, boolean hierarchical) { 524 CacheToken ct = new CacheToken(); 525 ct.request = "{\"hierarchical\" : "+(hierarchical ? "true" : "false")+", \"url\": \""+Utilities.escapeJson(url)+"\"}\r\n"; 526 ct.key = String.valueOf(hashJson(ct.request)); 527 return ct; 528 } 529 530 public void nameCacheToken(ValueSet vs, CacheToken ct) { 531 if (vs != null) { 532 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 533 if (inc.hasSystem()) { 534 ct.setName(inc.getSystem()); 535 ct.hasVersion = inc.hasVersion(); 536 } 537 } 538 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 539 if (inc.hasSystem()) { 540 ct.setName(inc.getSystem()); 541 ct.hasVersion = inc.hasVersion(); 542 } 543 } 544 for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains()) { 545 if (inc.hasSystem()) { 546 ct.setName(inc.getSystem()); 547 ct.hasVersion = inc.hasVersion(); 548 } 549 } 550 } 551 } 552 553 private String normalizeSystemPath(String path) { 554 return path.replace("/", "").replace('|','X'); 555 } 556 557 558 559 public NamedCache getNamedCache(CacheToken cacheToken) { 560 561 final String cacheName = cacheToken.name == null ? "null" : cacheToken.name; 562 563 NamedCache nc = caches.get(cacheName); 564 565 if (nc == null) { 566 nc = new NamedCache(); 567 nc.name = cacheName; 568 caches.put(nc.name, nc); 569 } 570 return nc; 571 } 572 573 public ValueSetExpansionOutcome getExpansion(CacheToken cacheToken) { 574 synchronized (lock) { 575 NamedCache nc = getNamedCache(cacheToken); 576 CacheEntry e = nc.map.get(cacheToken.key); 577 if (e == null) 578 return null; 579 else 580 return e.e; 581 } 582 } 583 584 public void cacheExpansion(CacheToken cacheToken, ValueSetExpansionOutcome res, boolean persistent) { 585 synchronized (lock) { 586 NamedCache nc = getNamedCache(cacheToken); 587 CacheEntry e = new CacheEntry(); 588 e.request = cacheToken.request; 589 e.persistent = persistent; 590 e.e = res; 591 store(cacheToken, persistent, nc, e); 592 } 593 } 594 595 public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) { 596 if (noCaching) { 597 return; 598 } 599 600 if ( !cacheErrors && 601 ( e.v!= null 602 && e.v.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED 603 && !cacheToken.hasVersion)) { 604 return; 605 } 606 607 boolean n = nc.map.containsKey(cacheToken.key); 608 nc.map.put(cacheToken.key, e); 609 if (persistent) { 610 if (n) { 611 for (int i = nc.list.size()- 1; i>= 0; i--) { 612 if (nc.list.get(i).request.equals(e.request)) { 613 nc.list.remove(i); 614 } 615 } 616 } 617 nc.list.add(e); 618 save(nc); 619 } 620 } 621 622 public ValidationResult getValidation(CacheToken cacheToken) { 623 if (cacheToken.key == null) { 624 return null; 625 } 626 synchronized (lock) { 627 requestCount++; 628 NamedCache nc = getNamedCache(cacheToken); 629 CacheEntry e = nc.map.get(cacheToken.key); 630 if (e == null) { 631 networkCount++; 632 return null; 633 } else { 634 hitCount++; 635 return new ValidationResult(e.v); 636 } 637 } 638 } 639 640 public void cacheValidation(CacheToken cacheToken, ValidationResult res, boolean persistent) { 641 if (cacheToken.key != null) { 642 synchronized (lock) { 643 NamedCache nc = getNamedCache(cacheToken); 644 CacheEntry e = new CacheEntry(); 645 e.request = cacheToken.request; 646 e.persistent = persistent; 647 e.v = new ValidationResult(res); 648 store(cacheToken, persistent, nc, e); 649 } 650 } 651 } 652 653 654 // persistence 655 656 public void save() { 657 658 } 659 660 private <K extends Resource> void save(K resource, String title) { 661 if (folder == null) 662 return; 663 664 try { 665 OutputStreamWriter sw = new OutputStreamWriter(ManagedFileAccess.outStream(Utilities.path(folder, title + CACHE_FILE_EXTENSION)), "UTF-8"); 666 667 JsonParser json = new JsonParser(); 668 json.setOutputStyle(OutputStyle.PRETTY); 669 670 sw.write(json.composeString(resource).trim()); 671 sw.close(); 672 } catch (Exception e) { 673 System.out.println("error saving capability statement "+e.getMessage()); 674 } 675 } 676 677 private void save(NamedCache nc) { 678 if (folder == null) 679 return; 680 681 try { 682 OutputStreamWriter sw = new OutputStreamWriter(ManagedFileAccess.outStream(Utilities.path(folder, nc.name+CACHE_FILE_EXTENSION)), "UTF-8"); 683 sw.write(ENTRY_MARKER+"\r\n"); 684 JsonParser json = new JsonParser(); 685 json.setOutputStyle(OutputStyle.PRETTY); 686 for (CacheEntry ce : nc.list) { 687 sw.write(ce.request.trim()); 688 sw.write(BREAK+"\r\n"); 689 if (ce.e != null) { 690 sw.write("e: {\r\n"); 691 if (ce.e.isFromServer()) 692 sw.write(" \"from-server\" : true,\r\n"); 693 if (ce.e.getValueset() != null) { 694 if (ce.e.getValueset().hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) { 695 sw.write(" \"source\" : "+Utilities.escapeJson(ce.e.getValueset().getUserString(UserDataNames.VS_EXPANSION_SOURCE)).trim()+",\r\n"); 696 } 697 sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n"); 698 } 699 sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n"); 700 } else if (ce.s != null) { 701 sw.write("s: {\r\n"); 702 sw.write(" \"result\" : "+ce.s.result+"\r\n}\r\n"); 703 } else { 704 sw.write("v: {\r\n"); 705 boolean first = true; 706 if (ce.v.getDisplay() != null) { 707 if (first) first = false; else sw.write(",\r\n"); 708 sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\""); 709 } 710 if (ce.v.getCode() != null) { 711 if (first) first = false; else sw.write(",\r\n"); 712 sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\""); 713 } 714 if (ce.v.getSystem() != null) { 715 if (first) first = false; else sw.write(",\r\n"); 716 sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\""); 717 } 718 if (ce.v.getVersion() != null) { 719 if (first) first = false; else sw.write(",\r\n"); 720 sw.write(" \"version\" : \""+Utilities.escapeJson(ce.v.getVersion()).trim()+"\""); 721 } 722 if (ce.v.getSeverity() != null) { 723 if (first) first = false; else sw.write(",\r\n"); 724 sw.write(" \"severity\" : "+"\""+ce.v.getSeverity().toCode().trim()+"\""+""); 725 } 726 if (ce.v.getMessage() != null) { 727 if (first) first = false; else sw.write(",\r\n"); 728 sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\""); 729 } 730 if (ce.v.getErrorClass() != null) { 731 if (first) first = false; else sw.write(",\r\n"); 732 sw.write(" \"class\" : \""+Utilities.escapeJson(ce.v.getErrorClass().toString())+"\""); 733 } 734 if (ce.v.getDefinition() != null) { 735 if (first) first = false; else sw.write(",\r\n"); 736 sw.write(" \"definition\" : \""+Utilities.escapeJson(ce.v.getDefinition()).trim()+"\""); 737 } 738 if (ce.v.getStatus() != null) { 739 if (first) first = false; else sw.write(",\r\n"); 740 sw.write(" \"status\" : \""+Utilities.escapeJson(ce.v.getStatus()).trim()+"\""); 741 } 742 if (ce.v.getServer() != null) { 743 if (first) first = false; else sw.write(",\r\n"); 744 sw.write(" \"server\" : \""+Utilities.escapeJson(ce.v.getServer()).trim()+"\""); 745 } 746 if (ce.v.isInactive()) { 747 if (first) first = false; else sw.write(",\r\n"); 748 sw.write(" \"inactive\" : true"); 749 } 750 if (ce.v.getUnknownSystems() != null) { 751 if (first) first = false; else sw.write(",\r\n"); 752 sw.write(" \"unknown-systems\" : \""+Utilities.escapeJson(CommaSeparatedStringBuilder.join(",", ce.v.getUnknownSystems())).trim()+"\""); 753 } 754 if (ce.v.getIssues() != null) { 755 if (first) first = false; else sw.write(",\r\n"); 756 OperationOutcome oo = new OperationOutcome(); 757 oo.setIssue(ce.v.getIssues()); 758 sw.write(" \"issues\" : "+json.composeString(oo).trim()+"\r\n"); 759 } 760 sw.write("\r\n}\r\n"); 761 } 762 sw.write(ENTRY_MARKER+"\r\n"); 763 } 764 sw.close(); 765 } catch (Exception e) { 766 System.out.println("error saving "+nc.name+": "+e.getMessage()); 767 } 768 } 769 770 private boolean isCapabilityCache(String fn) { 771 if (fn == null) { 772 return false; 773 } 774 return fn.startsWith(CAPABILITY_STATEMENT_TITLE) || fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE); 775 } 776 777 private void loadCapabilityCache(String fn) { 778 try { 779 String src = FileUtilities.fileToString(Utilities.path(folder, fn)); 780 String serverId = Utilities.getFileNameForName(fn).replace(CACHE_FILE_EXTENSION, ""); 781 serverId = serverId.substring(serverId.indexOf(".")+1); 782 serverId = serverId.substring(serverId.indexOf(".")+1); 783 String address = getServerForId(serverId); 784 if (address != null) { 785 JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(src); 786 Resource resource = new JsonParser().parse(o); 787 788 if (fn.startsWith(CAPABILITY_STATEMENT_TITLE)) { 789 this.capabilityStatementCache.put(address, (CapabilityStatement) resource); 790 } else if (fn.startsWith(TERMINOLOGY_CAPABILITIES_TITLE)) { 791 this.terminologyCapabilitiesCache.put(address, (TerminologyCapabilities) resource); 792 } 793 } 794 } catch (Exception e) { 795 e.printStackTrace(); 796 throw new FHIRException("Error loading " + fn + ": " + e.getMessage(), e); 797 } 798 } 799 800 private String getServerForId(String serverId) { 801 for (String n : serverMap.keySet()) { 802 if (serverMap.get(n).equals(serverId)) { 803 return n; 804 } 805 } 806 return null; 807 } 808 809 private CacheEntry getCacheEntry(String request, String resultString) throws IOException { 810 CacheEntry ce = new CacheEntry(); 811 ce.persistent = true; 812 ce.request = request; 813 char e = resultString.charAt(0); 814 resultString = resultString.substring(3); 815 JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(resultString); 816 String error = loadJS(o.get("error")); 817 if (e == 'e') { 818 if (o.has("valueSet")) { 819 ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server")); 820 if (o.has("source")) { 821 ce.e.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, o.get("source").getAsString()); 822 } 823 } else { 824 ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server")); 825 } 826 } else if (e == 's') { 827 ce.s = new SubsumesResult(o.get("result").getAsBoolean()); 828 } else { 829 String t = loadJS(o.get("severity")); 830 IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t); 831 String display = loadJS(o.get("display")); 832 String code = loadJS(o.get("code")); 833 String system = loadJS(o.get("system")); 834 String version = loadJS(o.get("version")); 835 String definition = loadJS(o.get("definition")); 836 String server = loadJS(o.get("server")); 837 String status = loadJS(o.get("status")); 838 boolean inactive = "true".equals(loadJS(o.get("inactive"))); 839 String unknownSystems = loadJS(o.get("unknown-systems")); 840 OperationOutcome oo = o.has("issues") ? (OperationOutcome) new JsonParser().parse(o.getAsJsonObject("issues")) : null; 841 t = loadJS(o.get("class")); 842 TerminologyServiceErrorClass errorClass = t == null ? null : TerminologyServiceErrorClass.valueOf(t) ; 843 ce.v = new ValidationResult(severity, error, system, version, new ConceptDefinitionComponent().setDisplay(display).setDefinition(definition).setCode(code), display, null).setErrorClass(errorClass); 844 ce.v.setUnknownSystems(CommaSeparatedStringBuilder.toSet(unknownSystems)); 845 ce.v.setServer(server); 846 ce.v.setStatus(inactive, status); 847 if (oo != null) { 848 ce.v.setIssues(oo.getIssue()); 849 } 850 } 851 return ce; 852 } 853 854 private void loadNamedCache(String fn) { 855 int c = 0; 856 try { 857 String src = FileUtilities.fileToString(Utilities.path(folder, fn)); 858 String title = fn.substring(0, fn.lastIndexOf(".")); 859 860 NamedCache nc = new NamedCache(); 861 nc.name = title; 862 863 if (src.startsWith("?")) 864 src = src.substring(1); 865 int i = src.indexOf(ENTRY_MARKER); 866 while (i > -1) { 867 c++; 868 String s = src.substring(0, i); 869 src = src.substring(i + ENTRY_MARKER.length() + 1); 870 i = src.indexOf(ENTRY_MARKER); 871 if (!Utilities.noString(s)) { 872 int j = s.indexOf(BREAK); 873 String request = s.substring(0, j); 874 String p = s.substring(j + BREAK.length() + 1).trim(); 875 876 CacheEntry cacheEntry = getCacheEntry(request, p); 877 878 nc.map.put(String.valueOf(hashJson(cacheEntry.request)), cacheEntry); 879 nc.list.add(cacheEntry); 880 } 881 caches.put(nc.name, nc); 882 } 883 } catch (Exception e) { 884 System.out.println("Error loading "+fn+": "+e.getMessage()+" entry "+c+" - ignoring it"); 885 e.printStackTrace(); 886 } 887 } 888 889 private void load() throws FHIRException, IOException { 890 IniFile ini = new IniFile(Utilities.path(folder, "servers.ini")); 891 if (ini.hasSection("servers")) { 892 for (String n : ini.getPropertyNames("servers")) { 893 serverMap.put(ini.getStringProperty("servers", n), n); 894 } 895 } 896 897 for (String fn : ManagedFileAccess.file(folder).list()) { 898 if (fn.endsWith(CACHE_FILE_EXTENSION) && !fn.equals("validation" + CACHE_FILE_EXTENSION)) { 899 try { 900 if (isCapabilityCache(fn)) { 901 loadCapabilityCache(fn); 902 } else { 903 loadNamedCache(fn); 904 } 905 } catch (FHIRException e) { 906 throw e; 907 } 908 } 909 } 910 try { 911 File f = ManagedFileAccess.file(Utilities.path(folder, "vs-externals.json")); 912 if (f.exists()) { 913 org.hl7.fhir.utilities.json.model.JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(f); 914 for (JsonProperty p : json.getProperties()) { 915 if (p.getValue().isJsonNull()) { 916 vsCache.put(p.getName(), null); 917 } else { 918 org.hl7.fhir.utilities.json.model.JsonObject j = p.getValue().asJsonObject(); 919 vsCache.put(p.getName(), new SourcedValueSetEntry(j.asString("server"), j.asString("filename"))); 920 } 921 } 922 } 923 } catch (Exception e) { 924 System.out.println("Error loading vs external cache: "+e.getMessage()); 925 } 926 try { 927 File f = ManagedFileAccess.file(Utilities.path(folder, "cs-externals.json")); 928 if (f.exists()) { 929 org.hl7.fhir.utilities.json.model.JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(f); 930 for (JsonProperty p : json.getProperties()) { 931 if (p.getValue().isJsonNull()) { 932 csCache.put(p.getName(), null); 933 } else { 934 org.hl7.fhir.utilities.json.model.JsonObject j = p.getValue().asJsonObject(); 935 csCache.put(p.getName(), new SourcedCodeSystemEntry(j.asString("server"), j.asString("filename"))); 936 } 937 } 938 } 939 } catch (Exception e) { 940 System.out.println("Error loading vs external cache: "+e.getMessage()); 941 } 942 } 943 944 private String loadJS(JsonElement e) { 945 if (e == null) 946 return null; 947 if (!(e instanceof JsonPrimitive)) 948 return null; 949 String s = e.getAsString(); 950 if ("".equals(s)) 951 return null; 952 return s; 953 } 954 955 public String hashJson(String s) { 956 return String.valueOf(s.trim().hashCode()); 957 } 958 959 // management 960 961 public String summary(ValueSet vs) { 962 if (vs == null) 963 return "null"; 964 965 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 966 for (ConceptSetComponent cc : vs.getCompose().getInclude()) 967 b.append("Include "+getIncSummary(cc)); 968 for (ConceptSetComponent cc : vs.getCompose().getExclude()) 969 b.append("Exclude "+getIncSummary(cc)); 970 return b.toString(); 971 } 972 973 private String getIncSummary(ConceptSetComponent cc) { 974 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 975 for (UriType vs : cc.getValueSet()) 976 b.append(vs.asStringValue()); 977 String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : ""; 978 String system = cc.getSystem(); 979 if (cc.hasConcept()) 980 return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd; 981 if (cc.hasFilter()) { 982 String s = ""; 983 for (ConceptSetFilterComponent f : cc.getFilter()) { 984 if (!Utilities.noString(s)) 985 s = s + " & "; 986 s = s + f.getProperty()+" "+(f.hasOp() ? f.getOp().toCode() : "?")+" "+f.getValue(); 987 } 988 return "from "+system+" where "+s+vsd; 989 } 990 return "All codes from "+system+vsd; 991 } 992 993 public String summary(Coding code) { 994 return code.getSystem()+"#"+code.getCode()+(code.hasDisplay() ? ": \""+code.getDisplay()+"\"" : ""); 995 } 996 997 public String summary(CodeableConcept code) { 998 StringBuilder b = new StringBuilder(); 999 b.append("{"); 1000 boolean first = true; 1001 for (Coding c : code.getCoding()) { 1002 if (first) first = false; else b.append(","); 1003 b.append(summary(c)); 1004 } 1005 b.append("}: \""); 1006 b.append(code.getText()); 1007 b.append("\""); 1008 return b.toString(); 1009 } 1010 1011 public void removeCS(String url) { 1012 synchronized (lock) { 1013 String name = getSystemNameKeyGenerator().getNameForSystem(url); 1014 if (caches.containsKey(name)) { 1015 caches.remove(name); 1016 } 1017 } 1018 } 1019 1020 public String getFolder() { 1021 return folder; 1022 } 1023 1024 public Map<String, String> servers() { 1025 Map<String, String> servers = new HashMap<>(); 1026// servers.put("http://local.fhir.org/r2", "tx.fhir.org"); 1027// servers.put("http://local.fhir.org/r3", "tx.fhir.org"); 1028// servers.put("http://local.fhir.org/r4", "tx.fhir.org"); 1029// servers.put("http://local.fhir.org/r5", "tx.fhir.org"); 1030// 1031// servers.put("http://tx-dev.fhir.org/r2", "tx.fhir.org"); 1032// servers.put("http://tx-dev.fhir.org/r3", "tx.fhir.org"); 1033// servers.put("http://tx-dev.fhir.org/r4", "tx.fhir.org"); 1034// servers.put("http://tx-dev.fhir.org/r5", "tx.fhir.org"); 1035 1036 servers.put("http://tx.fhir.org/r2", "tx.fhir.org"); 1037 servers.put("http://tx.fhir.org/r3", "tx.fhir.org"); 1038 servers.put("http://tx.fhir.org/r4", "tx.fhir.org"); 1039 servers.put("http://tx.fhir.org/r5", "tx.fhir.org"); 1040 1041 return servers; 1042 } 1043 1044 public boolean hasValueSet(String canonical) { 1045 return vsCache.containsKey(canonical); 1046 } 1047 1048 public boolean hasCodeSystem(String canonical) { 1049 return csCache.containsKey(canonical); 1050 } 1051 1052 public SourcedValueSet getValueSet(String canonical) { 1053 SourcedValueSetEntry sp = vsCache.get(canonical); 1054 if (sp == null || folder == null) { 1055 return null; 1056 } else { 1057 try { 1058 return new SourcedValueSet(sp.getServer(), sp.getFilename() == null ? null : (ValueSet) new JsonParser().parse(ManagedFileAccess.inStream(Utilities.path(folder, sp.getFilename())))); 1059 } catch (Exception e) { 1060 return null; 1061 } 1062 } 1063 } 1064 1065 public SourcedCodeSystem getCodeSystem(String canonical) { 1066 SourcedCodeSystemEntry sp = csCache.get(canonical); 1067 if (sp == null || folder == null) { 1068 return null; 1069 } else { 1070 try { 1071 return new SourcedCodeSystem(sp.getServer(), sp.getFilename() == null ? null : (CodeSystem) new JsonParser().parse(ManagedFileAccess.inStream(Utilities.path(folder, sp.getFilename())))); 1072 } catch (Exception e) { 1073 return null; 1074 } 1075 } 1076 } 1077 1078 public void cacheValueSet(String canonical, SourcedValueSet svs) { 1079 if (canonical == null) { 1080 return; 1081 } 1082 try { 1083 if (svs == null) { 1084 vsCache.put(canonical, null); 1085 } else { 1086 String uuid = UUIDUtilities.makeUuidLC(); 1087 String fn = "vs-"+uuid+".json"; 1088 if (folder != null) { 1089 new JsonParser().compose(ManagedFileAccess.outStream(Utilities.path(folder, fn)), svs.getVs()); 1090 } 1091 vsCache.put(canonical, new SourcedValueSetEntry(svs.getServer(), fn)); 1092 } 1093 org.hl7.fhir.utilities.json.model.JsonObject j = new org.hl7.fhir.utilities.json.model.JsonObject(); 1094 for (String k : vsCache.keySet()) { 1095 SourcedValueSetEntry sve = vsCache.get(k); 1096 if (sve == null) { 1097 j.add(k, new JsonNull()); 1098 } else { 1099 org.hl7.fhir.utilities.json.model.JsonObject e = new org.hl7.fhir.utilities.json.model.JsonObject(); 1100 e.set("server", sve.getServer()); 1101 if (sve.getFilename() != null) { 1102 e.set("filename", sve.getFilename()); 1103 } 1104 j.add(k, e); 1105 } 1106 } 1107 if (folder != null) { 1108 org.hl7.fhir.utilities.json.parser.JsonParser.compose(j, ManagedFileAccess.file(Utilities.path(folder, "vs-externals.json")), true); 1109 } 1110 } catch (Exception e) { 1111 e.printStackTrace(); 1112 } 1113 } 1114 1115 public void cacheCodeSystem(String canonical, SourcedCodeSystem scs) { 1116 if (canonical == null) { 1117 return; 1118 } 1119 try { 1120 if (scs == null) { 1121 csCache.put(canonical, null); 1122 } else { 1123 String uuid = UUIDUtilities.makeUuidLC(); 1124 String fn = "cs-"+uuid+".json"; 1125 if (folder != null) { 1126 new JsonParser().compose(ManagedFileAccess.outStream(Utilities.path(folder, fn)), scs.getCs()); 1127 } 1128 csCache.put(canonical, new SourcedCodeSystemEntry(scs.getServer(), fn)); 1129 } 1130 org.hl7.fhir.utilities.json.model.JsonObject j = new org.hl7.fhir.utilities.json.model.JsonObject(); 1131 for (String k : csCache.keySet()) { 1132 SourcedCodeSystemEntry sve = csCache.get(k); 1133 if (sve == null) { 1134 j.add(k, new JsonNull()); 1135 } else { 1136 org.hl7.fhir.utilities.json.model.JsonObject e = new org.hl7.fhir.utilities.json.model.JsonObject(); 1137 e.set("server", sve.getServer()); 1138 if (sve.getFilename() != null) { 1139 e.set("filename", sve.getFilename()); 1140 } 1141 j.add(k, e); 1142 } 1143 } 1144 if (folder != null) { 1145 org.hl7.fhir.utilities.json.parser.JsonParser.compose(j, ManagedFileAccess.file(Utilities.path(folder, "cs-externals.json")), true); 1146 } 1147 } catch (Exception e) { 1148 e.printStackTrace(); 1149 } 1150 } 1151 1152 public CacheToken generateSubsumesToken(ValidationOptions options, Coding parent, Coding child, Parameters expParameters) { 1153 try { 1154 CacheToken ct = new CacheToken(); 1155 if (parent.hasSystem()) { 1156 ct.setName(parent.getSystem()); 1157 } 1158 if (child.hasSystem()) { 1159 ct.setName(child.getSystem()); 1160 } 1161 ct.hasVersion = parent.hasVersion() || child.hasVersion(); 1162 JsonParser json = new JsonParser(); 1163 json.setOutputStyle(OutputStyle.PRETTY); 1164 String expJS = json.composeString(expParameters); 1165 ct.request = "{\"op\": \"subsumes\", \"parent\" : "+json.composeString(parent, "code")+", \"child\" :"+json.composeString(child, "code")+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}"; 1166 ct.key = String.valueOf(hashJson(ct.request)); 1167 return ct; 1168 } catch (IOException e) { 1169 throw new Error(e); 1170 } 1171 } 1172 1173 public Boolean getSubsumes(CacheToken cacheToken) { 1174 if (cacheToken.key == null) { 1175 return null; 1176 } 1177 synchronized (lock) { 1178 requestCount++; 1179 NamedCache nc = getNamedCache(cacheToken); 1180 CacheEntry e = nc.map.get(cacheToken.key); 1181 if (e == null) { 1182 networkCount++; 1183 return null; 1184 } else { 1185 hitCount++; 1186 return e.s.result; 1187 } 1188 } 1189 1190 } 1191 1192 public void cacheSubsumes(CacheToken cacheToken, Boolean b, boolean persistent) { 1193 if (cacheToken.key != null) { 1194 synchronized (lock) { 1195 NamedCache nc = getNamedCache(cacheToken); 1196 CacheEntry e = new CacheEntry(); 1197 e.request = cacheToken.request; 1198 e.persistent = persistent; 1199 e.s = new SubsumesResult(b); 1200 store(cacheToken, persistent, nc, e); 1201 } 1202 } 1203 } 1204 1205 1206}