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