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