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