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