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