001package org.hl7.fhir.r4b.context; 002 003import java.io.File; 004 005/* 006 Copyright (c) 2011+, HL7, Inc. 007 All rights reserved. 008 009 Redistribution and use in source and binary forms, with or without modification, 010 are permitted provided that the following conditions are met: 011 012 * Redistributions of source code must retain the above copyright notice, this 013 list of conditions and the following disclaimer. 014 * Redistributions in binary form must reproduce the above copyright notice, 015 this list of conditions and the following disclaimer in the documentation 016 and/or other materials provided with the distribution. 017 * Neither the name of HL7 nor the names of its contributors may be used to 018 endorse or promote products derived from this software without specific 019 prior written permission. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 024 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 025 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 026 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 028 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 029 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 030 POSSIBILITY OF SUCH DAMAGE. 031 032 */ 033 034 035import java.io.FileNotFoundException; 036import java.io.IOException; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.Comparator; 040import java.util.Date; 041import java.util.HashMap; 042import java.util.HashSet; 043import java.util.List; 044import java.util.Locale; 045import java.util.Map; 046import java.util.Set; 047 048import org.apache.commons.lang3.StringUtils; 049import org.fhir.ucum.UcumService; 050import org.hl7.fhir.exceptions.DefinitionException; 051import org.hl7.fhir.exceptions.FHIRException; 052import org.hl7.fhir.exceptions.NoTerminologyServiceException; 053import org.hl7.fhir.exceptions.TerminologyServiceException; 054import org.hl7.fhir.r4b.conformance.ProfileUtilities; 055import org.hl7.fhir.r4b.context.BaseWorkerContext.ResourceProxy; 056import org.hl7.fhir.r4b.context.CanonicalResourceManager.CanonicalResourceProxy; 057import org.hl7.fhir.r4b.context.IWorkerContext.PackageVersion; 058import org.hl7.fhir.r4b.context.IWorkerContext.ILoggingService.LogCategory; 059import org.hl7.fhir.r4b.context.TerminologyCache.CacheToken; 060import org.hl7.fhir.r4b.model.BooleanType; 061import org.hl7.fhir.r4b.model.Bundle; 062import org.hl7.fhir.r4b.model.CanonicalResource; 063import org.hl7.fhir.r4b.model.CanonicalType; 064import org.hl7.fhir.r4b.model.CapabilityStatement; 065import org.hl7.fhir.r4b.model.CodeSystem; 066import org.hl7.fhir.r4b.model.CodeSystem.CodeSystemContentMode; 067import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent; 068import org.hl7.fhir.r4b.model.CodeSystem.ConceptPropertyComponent; 069import org.hl7.fhir.r4b.model.CodeableConcept; 070import org.hl7.fhir.r4b.model.Coding; 071import org.hl7.fhir.r4b.model.ConceptMap; 072import org.hl7.fhir.r4b.model.Constants; 073import org.hl7.fhir.r4b.model.ElementDefinition; 074import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent; 075import org.hl7.fhir.r4b.model.Enumerations.PublicationStatus; 076import org.hl7.fhir.r4b.model.Identifier; 077import org.hl7.fhir.r4b.model.ImplementationGuide; 078import org.hl7.fhir.r4b.model.Library; 079import org.hl7.fhir.r4b.model.Measure; 080import org.hl7.fhir.r4b.model.NamingSystem; 081import org.hl7.fhir.r4b.model.NamingSystem.NamingSystemIdentifierType; 082import org.hl7.fhir.r4b.model.NamingSystem.NamingSystemUniqueIdComponent; 083import org.hl7.fhir.r4b.model.OperationDefinition; 084import org.hl7.fhir.r4b.model.OperationOutcome; 085import org.hl7.fhir.r4b.model.Parameters; 086import org.hl7.fhir.r4b.model.Parameters.ParametersParameterComponent; 087import org.hl7.fhir.r4b.model.PlanDefinition; 088import org.hl7.fhir.r4b.model.Questionnaire; 089import org.hl7.fhir.r4b.model.Reference; 090import org.hl7.fhir.r4b.model.Resource; 091import org.hl7.fhir.r4b.model.SearchParameter; 092import org.hl7.fhir.r4b.model.StringType; 093import org.hl7.fhir.r4b.model.StructureDefinition; 094import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule; 095import org.hl7.fhir.r4b.model.StructureMap; 096import org.hl7.fhir.r4b.model.TerminologyCapabilities; 097import org.hl7.fhir.r4b.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent; 098import org.hl7.fhir.r4b.model.TerminologyCapabilities.TerminologyCapabilitiesExpansionParameterComponent; 099import org.hl7.fhir.r4b.model.UriType; 100import org.hl7.fhir.r4b.model.ValueSet; 101import org.hl7.fhir.r4b.model.Bundle.BundleEntryComponent; 102import org.hl7.fhir.r4b.model.Bundle.BundleType; 103import org.hl7.fhir.r4b.model.Bundle.HTTPVerb; 104import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent; 105import org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent; 106import org.hl7.fhir.r4b.renderers.OperationOutcomeRenderer; 107import org.hl7.fhir.r4b.terminologies.CodeSystemUtilities; 108import org.hl7.fhir.r4b.terminologies.TerminologyClient; 109import org.hl7.fhir.r4b.terminologies.ValueSetCheckerSimple; 110import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 111import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 112import org.hl7.fhir.r4b.terminologies.ValueSetExpanderSimple; 113import org.hl7.fhir.r4b.utils.ToolingExtensions; 114import org.hl7.fhir.r4b.utils.validation.ValidationContextCarrier; 115import org.hl7.fhir.utilities.OIDUtils; 116import org.hl7.fhir.utilities.TimeTracker; 117import org.hl7.fhir.utilities.ToolingClientLogger; 118import org.hl7.fhir.utilities.TranslationServices; 119import org.hl7.fhir.utilities.Utilities; 120import org.hl7.fhir.utilities.VersionUtilities; 121import org.hl7.fhir.utilities.i18n.I18nBase; 122import org.hl7.fhir.utilities.i18n.I18nConstants; 123import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 124import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 125import org.hl7.fhir.utilities.validation.ValidationOptions; 126import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode; 127 128import com.google.gson.JsonObject; 129 130import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; 131 132public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext{ 133 134 public class ResourceProxy { 135 private Resource resource; 136 private CanonicalResourceProxy proxy; 137 138 public ResourceProxy(Resource resource) { 139 super(); 140 this.resource = resource; 141 } 142 public ResourceProxy(CanonicalResourceProxy proxy) { 143 super(); 144 this.proxy = proxy; 145 } 146 147 public Resource getResource() { 148 return resource != null ? resource : proxy.getResource(); 149 } 150 151 public String getUrl() { 152 if (resource == null) { 153 return proxy.getUrl(); 154 } else if (resource instanceof CanonicalResource) { 155 return ((CanonicalResource) resource).getUrl(); 156 } else { 157 return null; 158 } 159 } 160 161 } 162 163 public class MetadataResourceVersionComparator<T extends CanonicalResource> implements Comparator<T> { 164 165 private List<T> list; 166 167 public MetadataResourceVersionComparator(List<T> list) { 168 this.list = list; 169 } 170 171 @Override 172 public int compare(T arg1, T arg2) { 173 String v1 = arg1.getVersion(); 174 String v2 = arg2.getVersion(); 175 if (v1 == null && v2 == null) { 176 return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order 177 } else if (v1 == null) { 178 return -1; 179 } else if (v2 == null) { 180 return 1; 181 } else { 182 String mm1 = VersionUtilities.getMajMin(v1); 183 String mm2 = VersionUtilities.getMajMin(v2); 184 if (mm1 == null || mm2 == null) { 185 return v1.compareTo(v2); 186 } else { 187 return mm1.compareTo(mm2); 188 } 189 } 190 } 191 } 192 193 private Object lock = new Object(); // used as a lock for the data that follows 194 protected String version; // although the internal resources are all R5, the version of FHIR they describe may not be 195 private String cacheId; 196 private boolean isTxCaching; 197 private Set<String> cached = new HashSet<>(); 198 199 private Map<String, Map<String, ResourceProxy>> allResourcesById = new HashMap<String, Map<String, ResourceProxy>>(); 200 // all maps are to the full URI 201 private CanonicalResourceManager<CodeSystem> codeSystems = new CanonicalResourceManager<CodeSystem>(false); 202 private Set<String> supportedCodeSystems = new HashSet<String>(); 203 private Set<String> unsupportedCodeSystems = new HashSet<String>(); // know that the terminology server doesn't support them 204 private CanonicalResourceManager<ValueSet> valueSets = new CanonicalResourceManager<ValueSet>(false); 205 private CanonicalResourceManager<ConceptMap> maps = new CanonicalResourceManager<ConceptMap>(false); 206 protected CanonicalResourceManager<StructureMap> transforms = new CanonicalResourceManager<StructureMap>(false); 207 private CanonicalResourceManager<StructureDefinition> structures = new CanonicalResourceManager<StructureDefinition>(false); 208 private CanonicalResourceManager<Measure> measures = new CanonicalResourceManager<Measure>(false); 209 private CanonicalResourceManager<Library> libraries = new CanonicalResourceManager<Library>(false); 210 private CanonicalResourceManager<ImplementationGuide> guides = new CanonicalResourceManager<ImplementationGuide>(false); 211 private CanonicalResourceManager<CapabilityStatement> capstmts = new CanonicalResourceManager<CapabilityStatement>(false); 212 private CanonicalResourceManager<SearchParameter> searchParameters = new CanonicalResourceManager<SearchParameter>(false); 213 private CanonicalResourceManager<Questionnaire> questionnaires = new CanonicalResourceManager<Questionnaire>(false); 214 private CanonicalResourceManager<OperationDefinition> operations = new CanonicalResourceManager<OperationDefinition>(false); 215 private CanonicalResourceManager<PlanDefinition> plans = new CanonicalResourceManager<PlanDefinition>(false); 216 private CanonicalResourceManager<NamingSystem> systems = new CanonicalResourceManager<NamingSystem>(false); 217 218 private UcumService ucumService; 219 protected Map<String, byte[]> binaries = new HashMap<String, byte[]>(); 220 protected Map<String, String> oidCache = new HashMap<>(); 221 222 protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String,ValidationResult>>(); 223 protected String tsServer; 224 protected String name; 225 private boolean allowLoadingDuplicates; 226 227 protected TerminologyClient txClient; 228 private Set<String> codeSystemsUsed = new HashSet<>(); 229 protected ToolingClientLogger txLog; 230 private TerminologyCapabilities txcaps; 231 private boolean canRunWithoutTerminology; 232 protected boolean noTerminologyServer; 233 private int expandCodesLimit = 1000; 234 protected ILoggingService logger; 235 protected Parameters expParameters; 236 private TranslationServices translator = new NullTranslator(); 237 protected TerminologyCache txCache; 238 protected TimeTracker clock; 239 private boolean tlogging = true; 240 private ICanonicalResourceLocator locator; 241 protected String userAgent; 242 243 public BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException { 244 txCache = new TerminologyCache(lock, null); 245 setValidationMessageLanguage(getLocale()); 246 clock = new TimeTracker(); 247 } 248 249 public BaseWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException { 250 txCache = new TerminologyCache(lock, null); 251 setValidationMessageLanguage(locale); 252 clock = new TimeTracker(); 253 } 254 255 public BaseWorkerContext(CanonicalResourceManager<CodeSystem> codeSystems, CanonicalResourceManager<ValueSet> valueSets, CanonicalResourceManager<ConceptMap> maps, CanonicalResourceManager<StructureDefinition> profiles, 256 CanonicalResourceManager<ImplementationGuide> guides) throws FileNotFoundException, IOException, FHIRException { 257 this(); 258 this.codeSystems = codeSystems; 259 this.valueSets = valueSets; 260 this.maps = maps; 261 this.structures = profiles; 262 this.guides = guides; 263 clock = new TimeTracker(); 264 } 265 266 protected void copy(BaseWorkerContext other) { 267 synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet 268 allResourcesById.putAll(other.allResourcesById); 269 translator = other.translator; 270 codeSystems.copy(other.codeSystems); 271 txcaps = other.txcaps; 272 valueSets.copy(other.valueSets); 273 maps.copy(other.maps); 274 transforms.copy(other.transforms); 275 structures.copy(other.structures); 276 searchParameters.copy(other.searchParameters); 277 plans.copy(other.plans); 278 questionnaires.copy(other.questionnaires); 279 operations.copy(other.operations); 280 systems.copy(other.systems); 281 guides.copy(other.guides); 282 capstmts.copy(other.capstmts); 283 measures.copy(other.measures); 284 libraries.copy(libraries); 285 286 allowLoadingDuplicates = other.allowLoadingDuplicates; 287 tsServer = other.tsServer; 288 name = other.name; 289 txClient = other.txClient; 290 txLog = other.txLog; 291 txcaps = other.txcaps; 292 canRunWithoutTerminology = other.canRunWithoutTerminology; 293 noTerminologyServer = other.noTerminologyServer; 294 if (other.txCache != null) 295 txCache = other.txCache.copy(); 296 expandCodesLimit = other.expandCodesLimit; 297 logger = other.logger; 298 expParameters = other.expParameters; 299 } 300 } 301 302 303 public void cacheResource(Resource r) throws FHIRException { 304 cacheResourceFromPackage(r, null); 305 } 306 307 308 public void registerResourceFromPackage(CanonicalResourceProxy r, PackageVersion packageInfo) throws FHIRException { 309 synchronized (lock) { 310 Map<String, ResourceProxy> map = allResourcesById.get(r.getType()); 311 if (map == null) { 312 map = new HashMap<String, ResourceProxy>(); 313 allResourcesById.put(r.getType(), map); 314 } 315 if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) { 316 map.put(r.getId(), new ResourceProxy(r)); 317 } 318 319 String url = r.getUrl(); 320 if (!allowLoadingDuplicates && hasResource(r.getType(), url)) { 321 // spcial workaround for known problems with existing packages 322 if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) { 323 return; 324 } 325 throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url)); 326 } 327 switch(r.getType()) { 328 case "StructureDefinition": 329 if ("1.4.0".equals(version)) { 330 StructureDefinition sd = (StructureDefinition) r.getResource(); 331 fixOldSD(sd); 332 } 333 structures.register(r, packageInfo); 334 break; 335 case "ValueSet": 336 valueSets.register(r, packageInfo); 337 break; 338 case "CodeSystem": 339 codeSystems.register(r, packageInfo); 340 break; 341 case "ImplementationGuide": 342 guides.register(r, packageInfo); 343 break; 344 case "CapabilityStatement": 345 capstmts.register(r, packageInfo); 346 break; 347 case "Measure": 348 measures.register(r, packageInfo); 349 break; 350 case "Library": 351 libraries.register(r, packageInfo); 352 break; 353 case "SearchParameter": 354 searchParameters.register(r, packageInfo); 355 break; 356 case "PlanDefinition": 357 plans.register(r, packageInfo); 358 break; 359 case "OperationDefinition": 360 operations.register(r, packageInfo); 361 break; 362 case "Questionnaire": 363 questionnaires.register(r, packageInfo); 364 break; 365 case "ConceptMap": 366 maps.register(r, packageInfo); 367 break; 368 case "StructureMap": 369 transforms.register(r, packageInfo); 370 break; 371 case "NamingSystem": 372 systems.register(r, packageInfo); 373 break; 374 } 375 } 376 } 377 378 public void cacheResourceFromPackage(Resource r, PackageVersion packageInfo) throws FHIRException { 379 synchronized (lock) { 380 Map<String, ResourceProxy> map = allResourcesById.get(r.fhirType()); 381 if (map == null) { 382 map = new HashMap<String, ResourceProxy>(); 383 allResourcesById.put(r.fhirType(), map); 384 } 385 if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) { 386 map.put(r.getId(), new ResourceProxy(r)); 387 } else { 388 System.out.println("Ingore "+r.fhirType()+"/"+r.getId()+" from package "+packageInfo.toString()); 389 } 390 391 if (r instanceof CodeSystem || r instanceof NamingSystem) { 392 oidCache.clear(); 393 } 394 395 if (r instanceof CanonicalResource) { 396 CanonicalResource m = (CanonicalResource) r; 397 String url = m.getUrl(); 398 if (!allowLoadingDuplicates && hasResource(r.getClass(), url)) { 399 // spcial workaround for known problems with existing packages 400 if (Utilities.existsInList(url, "http://hl7.org/fhir/SearchParameter/example")) { 401 return; 402 } 403 throw new DefinitionException(formatMessage(I18nConstants.DUPLICATE_RESOURCE_, url)); 404 } 405 if (r instanceof StructureDefinition) { 406 StructureDefinition sd = (StructureDefinition) m; 407 if ("1.4.0".equals(version)) { 408 fixOldSD(sd); 409 } 410 structures.see(sd, packageInfo); 411 } else if (r instanceof ValueSet) { 412 valueSets.see((ValueSet) m, packageInfo); 413 } else if (r instanceof CodeSystem) { 414 CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) r); 415 codeSystems.see((CodeSystem) m, packageInfo); 416 } else if (r instanceof ImplementationGuide) { 417 guides.see((ImplementationGuide) m, packageInfo); 418 } else if (r instanceof CapabilityStatement) { 419 capstmts.see((CapabilityStatement) m, packageInfo); 420 } else if (r instanceof Measure) { 421 measures.see((Measure) m, packageInfo); 422 } else if (r instanceof Library) { 423 libraries.see((Library) m, packageInfo); 424 } else if (r instanceof SearchParameter) { 425 searchParameters.see((SearchParameter) m, packageInfo); 426 } else if (r instanceof PlanDefinition) { 427 plans.see((PlanDefinition) m, packageInfo); 428 } else if (r instanceof OperationDefinition) { 429 operations.see((OperationDefinition) m, packageInfo); 430 } else if (r instanceof Questionnaire) { 431 questionnaires.see((Questionnaire) m, packageInfo); 432 } else if (r instanceof ConceptMap) { 433 maps.see((ConceptMap) m, packageInfo); 434 } else if (r instanceof StructureMap) { 435 transforms.see((StructureMap) m, packageInfo); 436 } else if (r instanceof NamingSystem) { 437 systems.see((NamingSystem) m, packageInfo); 438 } 439 } 440 } 441 } 442 443 public void fixOldSD(StructureDefinition sd) { 444 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension") && sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 445 sd.setSnapshot(null); 446 } 447 for (ElementDefinition ed : sd.getDifferential().getElement()) { 448 if (ed.getPath().equals("Extension.url") || ed.getPath().endsWith(".extension.url") ) { 449 ed.setMin(1); 450 if (ed.hasBase()) { 451 ed.getBase().setMin(1); 452 } 453 } 454 if ("extension".equals(ed.getSliceName())) { 455 ed.setSliceName(null); 456 } 457 } 458 } 459 460 /* 461 * Compare business versions, returning "true" if the candidate newer version is in fact newer than the oldVersion 462 * Comparison will work for strictly numeric versions as well as multi-level versions separated by ., -, _, : or space 463 * Failing that, it will do unicode-based character ordering. 464 * E.g. 1.5.3 < 1.14.3 465 * 2017-3-10 < 2017-12-7 466 * A3 < T2 467 */ 468 private boolean laterVersion(String newVersion, String oldVersion) { 469 // Compare business versions, retur 470 newVersion = newVersion.trim(); 471 oldVersion = oldVersion.trim(); 472 if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion)) { 473 return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion); 474 } else if (hasDelimiter(newVersion, oldVersion, ".")) { 475 return laterDelimitedVersion(newVersion, oldVersion, "\\."); 476 } else if (hasDelimiter(newVersion, oldVersion, "-")) { 477 return laterDelimitedVersion(newVersion, oldVersion, "\\-"); 478 } else if (hasDelimiter(newVersion, oldVersion, "_")) { 479 return laterDelimitedVersion(newVersion, oldVersion, "\\_"); 480 } else if (hasDelimiter(newVersion, oldVersion, ":")) { 481 return laterDelimitedVersion(newVersion, oldVersion, "\\:"); 482 } else if (hasDelimiter(newVersion, oldVersion, " ")) { 483 return laterDelimitedVersion(newVersion, oldVersion, "\\ "); 484 } else { 485 return newVersion.compareTo(oldVersion) > 0; 486 } 487 } 488 489 /* 490 * Returns true if both strings include the delimiter and have the same number of occurrences of it 491 */ 492 private boolean hasDelimiter(String s1, String s2, String delimiter) { 493 return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length; 494 } 495 496 private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) { 497 String[] newParts = newVersion.split(delimiter); 498 String[] oldParts = oldVersion.split(delimiter); 499 for (int i = 0; i < newParts.length; i++) { 500 if (!newParts[i].equals(oldParts[i])) { 501 return laterVersion(newParts[i], oldParts[i]); 502 } 503 } 504 // This should never happen 505 throw new Error(formatMessage(I18nConstants.DELIMITED_VERSIONS_HAVE_EXACT_MATCH_FOR_DELIMITER____VS_, delimiter, newParts, oldParts)); 506 } 507 508 protected <T extends CanonicalResource> void seeMetadataResource(T r, Map<String, T> map, List<T> list, boolean addId) throws FHIRException { 509// if (addId) 510 // map.put(r.getId(), r); // todo: why? 511 list.add(r); 512 if (r.hasUrl()) { 513 // first, this is the correct reosurce for this version (if it has a version) 514 if (r.hasVersion()) { 515 map.put(r.getUrl()+"|"+r.getVersion(), r); 516 } 517 // if we haven't get anything for this url, it's the correct version 518 if (!map.containsKey(r.getUrl())) { 519 map.put(r.getUrl(), r); 520 } else { 521 List<T> rl = new ArrayList<T>(); 522 for (T t : list) { 523 if (t.getUrl().equals(r.getUrl()) && !rl.contains(t)) { 524 rl.add(t); 525 } 526 } 527 Collections.sort(rl, new MetadataResourceVersionComparator<T>(list)); 528 map.put(r.getUrl(), rl.get(rl.size()-1)); 529 T latest = null; 530 for (T t : rl) { 531 if (VersionUtilities.versionsCompatible(t.getVersion(), r.getVersion())) { 532 latest = t; 533 } 534 } 535 if (latest != null) { // might be null if it's not using semver 536 map.put(r.getUrl()+"|"+VersionUtilities.getMajMin(latest.getVersion()), rl.get(rl.size()-1)); 537 } 538 } 539 } 540 } 541 542 @Override 543 public CodeSystem fetchCodeSystem(String system) { 544 if (system == null) { 545 return null; 546 } 547 if (system.contains("|")) { 548 String s = system.substring(0, system.indexOf("|")); 549 String v = system.substring(system.indexOf("|")+1); 550 return fetchCodeSystem(s, v); 551 } 552 CodeSystem cs; 553 synchronized (lock) { 554 cs = codeSystems.get(system); 555 } 556 if (cs == null && locator != null) { 557 locator.findResource(this, system); 558 synchronized (lock) { 559 cs = codeSystems.get(system); 560 } 561 } 562 return cs; 563 } 564 565 public CodeSystem fetchCodeSystem(String system, String version) { 566 if (version == null) { 567 return fetchCodeSystem(system); 568 } 569 CodeSystem cs; 570 synchronized (lock) { 571 cs = codeSystems.get(system, version); 572 } 573 if (cs == null && locator != null) { 574 locator.findResource(this, system); 575 synchronized (lock) { 576 cs = codeSystems.get(system); 577 } 578 } 579 return cs; 580 } 581 582 @Override 583 public boolean supportsSystem(String system) throws TerminologyServiceException { 584 synchronized (lock) { 585 if (codeSystems.has(system) && codeSystems.get(system).getContent() != CodeSystemContentMode.NOTPRESENT) { 586 return true; 587 } else if (supportedCodeSystems.contains(system)) { 588 return true; 589 } else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) { 590 return false; 591 } else { 592 if (noTerminologyServer) { 593 return false; 594 } 595 if (txcaps == null) { 596 try { 597 log("Terminology server: Check for supported code systems for "+system); 598 setTxCaps(txClient.getTerminologyCapabilities()); 599 } catch (Exception e) { 600 if (canRunWithoutTerminology) { 601 noTerminologyServer = true; 602 log("==============!! Running without terminology server !! =============="); 603 if (txClient!=null) { 604 log("txServer = "+txClient.getAddress()); 605 log("Error = "+e.getMessage()+""); 606 } 607 log("====================================================================="); 608 return false; 609 } else { 610 e.printStackTrace(); 611 throw new TerminologyServiceException(e); 612 } 613 } 614 if (supportedCodeSystems.contains(system)) { 615 return true; 616 } 617 } 618 } 619 return false; 620 } 621 } 622 623 private void log(String message) { 624 if (logger != null) { 625 logger.logMessage(message); 626 } else { 627 System.out.println(message); 628 } 629 } 630 631 632 protected void tlog(String msg) { 633 if (tlogging ) { 634 if (logger != null) { 635 logger.logDebugMessage(LogCategory.TX, msg); 636 } else { 637 System.out.println("-tx: "+msg); 638 } 639 } 640 } 641 642 // --- expansion support ------------------------------------------------------------------------------------------------------------ 643 644 public int getExpandCodesLimit() { 645 return expandCodesLimit; 646 } 647 648 public void setExpandCodesLimit(int expandCodesLimit) { 649 this.expandCodesLimit = expandCodesLimit; 650 } 651 652 @Override 653 public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException { 654 ValueSet vs = null; 655 vs = fetchResource(ValueSet.class, binding.getValueSet()); 656 if (vs == null) { 657 throw new FHIRException(formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, binding.getValueSet())); 658 } 659 return expandVS(vs, cacheOk, heirarchical); 660 } 661 662 @Override 663 public ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean hierarchical) throws TerminologyServiceException { 664 ValueSet vs = new ValueSet(); 665 vs.setStatus(PublicationStatus.ACTIVE); 666 vs.setCompose(new ValueSetComposeComponent()); 667 vs.getCompose().getInclude().add(inc); 668 CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical); 669 ValueSetExpansionOutcome res; 670 res = txCache.getExpansion(cacheToken); 671 if (res != null) { 672 return res; 673 } 674 Parameters p = expParameters.copy(); 675 p.setParameter("includeDefinition", false); 676 p.setParameter("excludeNested", !hierarchical); 677 678 boolean cached = addDependentResources(p, vs); 679 if (cached) { 680 p.addParameter().setName("cache-id").setValue(new StringType(cacheId)); 681 } 682 for (ConceptSetComponent incl : vs.getCompose().getInclude()) { 683 codeSystemsUsed.add(incl.getSystem()); 684 } 685 for (ConceptSetComponent incl : vs.getCompose().getExclude()) { 686 codeSystemsUsed.add(incl.getSystem()); 687 } 688 689 if (noTerminologyServer) { 690 return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE); 691 } 692 Map<String, String> params = new HashMap<String, String>(); 693 params.put("_limit", Integer.toString(expandCodesLimit )); 694 params.put("_incomplete", "true"); 695 tlog("$expand on "+txCache.summary(vs)); 696 try { 697 ValueSet result = txClient.expandValueset(vs, p, params); 698 res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); 699 } catch (Exception e) { 700 res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); 701 if (txLog != null) { 702 res.setTxLink(txLog.getLastId()); 703 } 704 } 705 txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); 706 return res; 707 } 708 709 @Override 710 public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) { 711 if (expParameters == null) 712 throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED)); 713 Parameters p = expParameters.copy(); 714 return expandVS(vs, cacheOk, heirarchical, false, p); 715 } 716 717 @Override 718 public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk) { 719 if (expParameters == null) 720 throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED)); 721 Parameters p = expParameters.copy(); 722 return expandVS(vs, cacheOk, heirarchical, incompleteOk, p); 723 } 724 725 public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk, Parameters p) { 726 if (p == null) { 727 throw new Error(formatMessage(I18nConstants.NO_PARAMETERS_PROVIDED_TO_EXPANDVS)); 728 } 729 if (vs.hasExpansion()) { 730 return new ValueSetExpansionOutcome(vs.copy()); 731 } 732 if (!vs.hasUrl()) { 733 throw new Error(formatMessage(I18nConstants.NO_VALUE_SET_IN_URL)); 734 } 735 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 736 codeSystemsUsed.add(inc.getSystem()); 737 } 738 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 739 codeSystemsUsed.add(inc.getSystem()); 740 } 741 742 CacheToken cacheToken = txCache.generateExpandToken(vs, heirarchical); 743 ValueSetExpansionOutcome res; 744 if (cacheOk) { 745 res = txCache.getExpansion(cacheToken); 746 if (res != null) { 747 return res; 748 } 749 } 750 p.setParameter("includeDefinition", false); 751 p.setParameter("excludeNested", !heirarchical); 752 if (incompleteOk) { 753 p.setParameter("incomplete-ok", true); 754 } 755 756 List<String> allErrors = new ArrayList<>(); 757 758 // ok, first we try to expand locally 759 ValueSetExpanderSimple vse = new ValueSetExpanderSimple(this); 760 try { 761 res = vse.expand(vs, p); 762 allErrors.addAll(vse.getAllErrors()); 763 if (res.getValueset() != null) { 764 if (!res.getValueset().hasUrl()) { 765 throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET)); 766 } 767 txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT); 768 return res; 769 } 770 } catch (Exception e) { 771 allErrors.addAll(vse.getAllErrors()); 772 e.printStackTrace(); 773 } 774 775 // if that failed, we try to expand on the server 776 if (addDependentResources(p, vs)) { 777 p.addParameter().setName("cache-id").setValue(new StringType(cacheId)); 778 } 779 780 if (noTerminologyServer) { 781 return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, allErrors); 782 } 783 Map<String, String> params = new HashMap<String, String>(); 784 params.put("_limit", Integer.toString(expandCodesLimit )); 785 params.put("_incomplete", "true"); 786 tlog("$expand on "+txCache.summary(vs)); 787 try { 788 ValueSet result = txClient.expandValueset(vs, p, params); 789 if (!result.hasUrl()) { 790 result.setUrl(vs.getUrl()); 791 } 792 if (!result.hasUrl()) { 793 throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET_2)); 794 } 795 res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); 796 } catch (Exception e) { 797 res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors).setTxLink(txLog == null ? null : txLog.getLastId()); 798 } 799 txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); 800 return res; 801 } 802 803 private boolean hasTooCostlyExpansion(ValueSet valueset) { 804 return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY); 805 } 806 // --- validate code ------------------------------------------------------------------------------- 807 808 @Override 809 public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display) { 810 assert options != null; 811 Coding c = new Coding(system, version, code, display); 812 return validateCode(options, c, null); 813 } 814 815 @Override 816 public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs) { 817 assert options != null; 818 Coding c = new Coding(system, version, code, display); 819 return validateCode(options, c, vs); 820 } 821 822 @Override 823 public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs) { 824 assert options != null; 825 Coding c = new Coding(null, code, null); 826 return validateCode(options.guessSystem(), c, vs); 827 } 828 829 830 @Override 831 public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) { 832 if (options == null) { 833 options = ValidationOptions.defaults(); 834 } 835 // 1st pass: what is in the cache? 836 // 2nd pass: What can we do internally 837 // 3rd pass: hit the server 838 for (CodingValidationRequest t : codes) { 839 t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs) : null); 840 if (t.getCoding().hasSystem()) { 841 codeSystemsUsed.add(t.getCoding().getSystem()); 842 } 843 if (txCache != null) { 844 t.setResult(txCache.getValidation(t.getCacheToken())); 845 } 846 } 847 if (options.isUseClient()) { 848 for (CodingValidationRequest t : codes) { 849 if (!t.hasResult()) { 850 try { 851 ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this); 852 ValidationResult res = vsc.validateCode(t.getCoding()); 853 if (txCache != null) { 854 txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT); 855 } 856 t.setResult(res); 857 } catch (Exception e) { 858 } 859 } 860 } 861 } 862 863 for (CodingValidationRequest t : codes) { 864 if (!t.hasResult()) { 865 String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem(); 866 if (!options.isUseServer()) { 867 t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS)); 868 } else if (unsupportedCodeSystems.contains(codeKey)) { 869 t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED)); 870 } else if (noTerminologyServer) { 871 t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE)); 872 } 873 } 874 } 875 876 if (expParameters == null) 877 throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED)); 878 // for those that that failed, we try to validate on the server 879 Bundle batch = new Bundle(); 880 batch.setType(BundleType.BATCH); 881 Set<String> systems = new HashSet<>(); 882 for (CodingValidationRequest t : codes) { 883 if (!t.hasResult()) { 884 Parameters pIn = new Parameters(); 885 pIn.addParameter().setName("coding").setValue(t.getCoding()); 886 if (options.isGuessSystem()) { 887 pIn.addParameter().setName("implySystem").setValue(new BooleanType(true)); 888 } 889 if (vs != null) { 890 pIn.addParameter().setName("valueSet").setResource(vs); 891 } 892 pIn.addParameter().setName("profile").setResource(expParameters); 893 setTerminologyOptions(options, pIn); 894 BundleEntryComponent be = batch.addEntry(); 895 be.setResource(pIn); 896 be.getRequest().setMethod(HTTPVerb.POST); 897 be.getRequest().setUrl("CodeSystem/$validate-code"); 898 be.setUserData("source", t); 899 systems.add(t.getCoding().getSystem()); 900 } 901 } 902 if (batch.getEntry().size() > 0) { 903 tlog("$batch validate for "+batch.getEntry().size()+" codes on systems "+systems.toString()); 904 if (txClient == null) { 905 throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE)); 906 } 907 if (txLog != null) { 908 txLog.clearLastId(); 909 } 910 Bundle resp = txClient.validateBatch(batch); 911 for (int i = 0; i < batch.getEntry().size(); i++) { 912 CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData("source"); 913 BundleEntryComponent r = resp.getEntry().get(i); 914 if (r.getResource() instanceof Parameters) { 915 t.setResult(processValidationResult((Parameters) r.getResource())); 916 if (txCache != null) { 917 txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT); 918 } 919 } else { 920 t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource())).setTxLink(txLog == null ? null : txLog.getLastId())); 921 } 922 } 923 } 924 } 925 926 private String getResponseText(Resource resource) { 927 if (resource instanceof OperationOutcome) { 928 return OperationOutcomeRenderer.toString((OperationOutcome) resource); 929 } 930 return "Todo"; 931 } 932 933 @Override 934 public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) { 935 ValidationContextCarrier ctxt = new ValidationContextCarrier(); 936 return validateCode(options, code, vs, ctxt); 937 } 938 939 @Override 940 public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { 941 if (options == null) { 942 options = ValidationOptions.defaults(); 943 } 944 945 if (code.hasSystem()) { 946 codeSystemsUsed.add(code.getSystem()); 947 } 948 CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null; 949 ValidationResult res = null; 950 if (txCache != null) { 951 res = txCache.getValidation(cacheToken); 952 } 953 if (res != null) { 954 return res; 955 } 956 957 if (options.isUseClient()) { 958 // ok, first we try to validate locally 959 try { 960 ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this, ctxt); 961 if (!vsc.isServerSide(code.getSystem())) { 962 res = vsc.validateCode(code); 963 if (txCache != null) { 964 txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); 965 } 966 return res; 967 } 968 } catch (Exception e) { 969 } 970 } 971 972 String codeKey = code.hasVersion() ? code.getSystem()+"|"+code.getVersion() : code.getSystem(); 973 if (!options.isUseServer()) { 974 return new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS); 975 } 976 if (unsupportedCodeSystems.contains(codeKey)) { 977 return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, code.getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); 978 } 979 980 // if that failed, we try to validate on the server 981 if (noTerminologyServer) { 982 return new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE); 983 } 984 String csumm = txCache != null ? txCache.summary(code) : null; 985 if (txCache != null) { 986 tlog("$validate "+csumm+" for "+ txCache.summary(vs)); 987 } else { 988 tlog("$validate "+csumm+" before cache exists"); 989 } 990 try { 991 Parameters pIn = new Parameters(); 992 pIn.addParameter().setName("coding").setValue(code); 993 if (options.isGuessSystem()) { 994 pIn.addParameter().setName("implySystem").setValue(new BooleanType(true)); 995 } 996 setTerminologyOptions(options, pIn); 997 res = validateOnServer(vs, pIn, options); 998 } catch (Exception e) { 999 res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog == null ? null : txLog.getLastId()).setErrorClass(TerminologyServiceErrorClass.SERVER_ERROR); 1000 } 1001 if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && !code.hasVersion()) { 1002 unsupportedCodeSystems.add(codeKey); 1003 } else if (txCache != null) { // we never cache unsuppoted code systems - we always keep trying (but only once per run) 1004 txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); 1005 } 1006 return res; 1007 } 1008 1009 private void setTerminologyOptions(ValidationOptions options, Parameters pIn) { 1010 if (!Utilities.noString(options.getLanguage())) { 1011 pIn.addParameter("displayLanguage", options.getLanguage()); 1012 } 1013 if (options.getValueSetMode() != ValueSetMode.ALL_CHECKS) { 1014 pIn.addParameter("valueSetMode", options.getValueSetMode().toString()); 1015 } 1016 if (options.versionFlexible()) { 1017 pIn.addParameter("default-to-latest-version", true); 1018 } 1019 } 1020 1021 @Override 1022 public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) { 1023 CacheToken cacheToken = txCache.generateValidationToken(options, code, vs); 1024 ValidationResult res = txCache.getValidation(cacheToken); 1025 if (res != null) { 1026 return res; 1027 } 1028 for (Coding c : code.getCoding()) { 1029 if (c.hasSystem()) { 1030 codeSystemsUsed.add(c.getSystem()); 1031 } 1032 } 1033 1034 if (options.isUseClient()) { 1035 // ok, first we try to validate locally 1036 try { 1037 ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this); 1038 res = vsc.validateCode(code); 1039 txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); 1040 return res; 1041 } catch (Exception e) { 1042 if (e instanceof NoTerminologyServiceException) { 1043 return new ValidationResult(IssueSeverity.ERROR, "No Terminology Service", TerminologyServiceErrorClass.NOSERVICE); 1044 } 1045 } 1046 } 1047 1048 if (!options.isUseServer()) { 1049 return new ValidationResult(IssueSeverity.WARNING, "Unable to validate code without using server", TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS); 1050 } 1051 1052 // if that failed, we try to validate on the server 1053 if (noTerminologyServer) { 1054 return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE); 1055 } 1056 tlog("$validate "+txCache.summary(code)+" for "+ txCache.summary(vs)); 1057 try { 1058 Parameters pIn = new Parameters(); 1059 pIn.addParameter().setName("codeableConcept").setValue(code); 1060 setTerminologyOptions(options, pIn); 1061 res = validateOnServer(vs, pIn, options); 1062 } catch (Exception e) { 1063 res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog.getLastId()); 1064 } 1065 txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); 1066 return res; 1067 } 1068 1069 private ValidationResult validateOnServer(ValueSet vs, Parameters pin, ValidationOptions options) throws FHIRException { 1070 boolean cache = false; 1071 if (vs != null) { 1072 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 1073 codeSystemsUsed.add(inc.getSystem()); 1074 } 1075 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 1076 codeSystemsUsed.add(inc.getSystem()); 1077 } 1078 } 1079 if (vs != null) { 1080 if (isTxCaching && cacheId != null && vs.getUrl() != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) { 1081 pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+vs.getVersion() : ""))); 1082 } else if (options.getVsAsUrl()){ 1083 pin.addParameter().setName("url").setValue(new StringType(vs.getUrl())); 1084 } else { 1085 pin.addParameter().setName("valueSet").setResource(vs); 1086 if (vs.getUrl() != null) { 1087 cached.add(vs.getUrl()+"|"+vs.getVersion()); 1088 } 1089 } 1090 cache = true; 1091 addDependentResources(pin, vs); 1092 } 1093 if (cache) { 1094 pin.addParameter().setName("cache-id").setValue(new StringType(cacheId)); 1095 } 1096 for (ParametersParameterComponent pp : pin.getParameter()) { 1097 if (pp.getName().equals("profile")) { 1098 throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT)); 1099 } 1100 } 1101 if (expParameters == null) { 1102 throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED)); 1103 } 1104 pin.addParameter().setName("profile").setResource(expParameters); 1105 if (txLog != null) { 1106 txLog.clearLastId(); 1107 } 1108 if (txClient == null) { 1109 throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE)); 1110 } 1111 Parameters pOut; 1112 if (vs == null) { 1113 pOut = txClient.validateCS(pin); 1114 } else { 1115 pOut = txClient.validateVS(pin); 1116 } 1117 return processValidationResult(pOut); 1118 } 1119 1120 private boolean addDependentResources(Parameters pin, ValueSet vs) { 1121 boolean cache = false; 1122 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 1123 cache = addDependentResources(pin, inc) || cache; 1124 } 1125 for (ConceptSetComponent inc : vs.getCompose().getExclude()) { 1126 cache = addDependentResources(pin, inc) || cache; 1127 } 1128 return cache; 1129 } 1130 1131 private boolean addDependentResources(Parameters pin, ConceptSetComponent inc) { 1132 boolean cache = false; 1133 for (CanonicalType c : inc.getValueSet()) { 1134 ValueSet vs = fetchResource(ValueSet.class, c.getValue()); 1135 if (vs != null) { 1136 pin.addParameter().setName("tx-resource").setResource(vs); 1137 if (isTxCaching && cacheId == null || !cached.contains(vs.getVUrl())) { 1138 cached.add(vs.getVUrl()); 1139 cache = true; 1140 } 1141 addDependentResources(pin, vs); 1142 } 1143 } 1144 CodeSystem cs = fetchResource(CodeSystem.class, inc.getSystem()); 1145 if (cs != null) { 1146 pin.addParameter().setName("tx-resource").setResource(cs); 1147 if (isTxCaching && cacheId == null || !cached.contains(cs.getVUrl())) { 1148 cached.add(cs.getVUrl()); 1149 cache = true; 1150 } 1151 // todo: supplements 1152 } 1153 return cache; 1154 } 1155 1156 public ValidationResult processValidationResult(Parameters pOut) { 1157 boolean ok = false; 1158 String message = "No Message returned"; 1159 String display = null; 1160 String system = null; 1161 String code = null; 1162 TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN; 1163 for (ParametersParameterComponent p : pOut.getParameter()) { 1164 if (p.hasValue()) { 1165 if (p.getName().equals("result")) { 1166 ok = ((BooleanType) p.getValue()).getValue().booleanValue(); 1167 } else if (p.getName().equals("message")) { 1168 message = ((StringType) p.getValue()).getValue(); 1169 } else if (p.getName().equals("display")) { 1170 display = ((StringType) p.getValue()).getValue(); 1171 } else if (p.getName().equals("system")) { 1172 system = ((StringType) p.getValue()).getValue(); 1173 } else if (p.getName().equals("code")) { 1174 code = ((StringType) p.getValue()).getValue(); 1175 } else if (p.getName().equals("cause")) { 1176 try { 1177 IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue()); 1178 if (it == IssueType.UNKNOWN) { 1179 err = TerminologyServiceErrorClass.UNKNOWN; 1180 } else if (it == IssueType.NOTFOUND) { 1181 err = TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED; 1182 } else if (it == IssueType.NOTSUPPORTED) { 1183 err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED; 1184 } else { 1185 err = null; 1186 } 1187 } catch (FHIRException e) { 1188 } 1189 } 1190 } 1191 } 1192 if (!ok) { 1193 return new ValidationResult(IssueSeverity.ERROR, message+" (from "+txClient.getAddress()+")", err).setTxLink(txLog.getLastId()); 1194 } else if (message != null && !message.equals("No Message returned")) { 1195 return new ValidationResult(IssueSeverity.WARNING, message+" (from "+txClient.getAddress()+")", system, new ConceptDefinitionComponent().setDisplay(display).setCode(code)).setTxLink(txLog.getLastId()); 1196 } else if (display != null) { 1197 return new ValidationResult(system, new ConceptDefinitionComponent().setDisplay(display).setCode(code)).setTxLink(txLog.getLastId()); 1198 } else { 1199 return new ValidationResult(system, new ConceptDefinitionComponent().setCode(code)).setTxLink(txLog.getLastId()); 1200 } 1201 } 1202 1203 // -------------------------------------------------------------------------------------------------------------------------------------------------------- 1204 1205 public void initTS(String cachePath) throws Exception { 1206 if (!new File(cachePath).exists()) { 1207 Utilities.createDirectory(cachePath); 1208 } 1209 txCache = new TerminologyCache(lock, cachePath); 1210 } 1211 1212 public void clearTSCache(String url) throws Exception { 1213 txCache.removeCS(url); 1214 } 1215 1216 public void clearTS() { 1217 txCache.clear(); 1218 } 1219 1220 1221 @Override 1222 public List<ConceptMap> findMapsForSource(String url) throws FHIRException { 1223 synchronized (lock) { 1224 List<ConceptMap> res = new ArrayList<ConceptMap>(); 1225 for (ConceptMap map : maps.getList()) { 1226 if (((Reference) map.getSource()).getReference().equals(url)) { 1227 res.add(map); 1228 } 1229 } 1230 return res; 1231 } 1232 } 1233 1234 public boolean isCanRunWithoutTerminology() { 1235 return canRunWithoutTerminology; 1236 } 1237 1238 public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) { 1239 this.canRunWithoutTerminology = canRunWithoutTerminology; 1240 } 1241 1242 public void setLogger(ILoggingService logger) { 1243 this.logger = logger; 1244 } 1245 1246 public Parameters getExpansionParameters() { 1247 return expParameters; 1248 } 1249 1250 public void setExpansionProfile(Parameters expParameters) { 1251 this.expParameters = expParameters; 1252 } 1253 1254 @Override 1255 public boolean isNoTerminologyServer() { 1256 return noTerminologyServer; 1257 } 1258 1259 public void setNoTerminologyServer(boolean noTerminologyServer) { 1260 this.noTerminologyServer = noTerminologyServer; 1261 } 1262 1263 public String getName() { 1264 return name; 1265 } 1266 1267 public void setName(String name) { 1268 this.name = name; 1269 } 1270 1271 @Override 1272 public Set<String> getResourceNamesAsSet() { 1273 Set<String> res = new HashSet<String>(); 1274 res.addAll(getResourceNames()); 1275 return res; 1276 } 1277 1278 public boolean isAllowLoadingDuplicates() { 1279 return allowLoadingDuplicates; 1280 } 1281 1282 public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) { 1283 this.allowLoadingDuplicates = allowLoadingDuplicates; 1284 } 1285 1286 @Override 1287 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException { 1288 return fetchResourceWithException(class_, uri, null); 1289 } 1290 1291 public <T extends Resource> T fetchResourceWithException(String cls, String uri) throws FHIRException { 1292 return fetchResourceWithException(cls, uri, null); 1293 } 1294 1295 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, CanonicalResource source) throws FHIRException { 1296 return fetchResourceWithException(class_, uri, null, source); 1297 } 1298 1299 @SuppressWarnings("unchecked") 1300 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri, String version, CanonicalResource source) throws FHIRException { 1301 if (uri == null) { 1302 return null; 1303 } 1304 1305 if (class_ == StructureDefinition.class) { 1306 uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs()); 1307 } 1308 synchronized (lock) { 1309 1310 if (uri.contains("|")) { 1311 version = uri.substring(uri.lastIndexOf("|")+1); 1312 uri = uri.substring(0, uri.lastIndexOf("|")); 1313 } 1314 if (uri.contains("#")) { 1315 uri = uri.substring(0, uri.indexOf("#")); 1316 } 1317 if (class_ == Resource.class || class_ == null) { 1318 if (structures.has(uri)) { 1319 return (T) structures.get(uri, version); 1320 } 1321 if (guides.has(uri)) { 1322 return (T) guides.get(uri, version); 1323 } 1324 if (capstmts.has(uri)) { 1325 return (T) capstmts.get(uri, version); 1326 } 1327 if (measures.has(uri)) { 1328 return (T) measures.get(uri, version); 1329 } 1330 if (libraries.has(uri)) { 1331 return (T) libraries.get(uri, version); 1332 } 1333 if (valueSets.has(uri)) { 1334 return (T) valueSets.get(uri, version); 1335 } 1336 if (codeSystems.has(uri)) { 1337 return (T) codeSystems.get(uri, version); 1338 } 1339 if (operations.has(uri)) { 1340 return (T) operations.get(uri, version); 1341 } 1342 if (searchParameters.has(uri)) { 1343 return (T) searchParameters.get(uri, version); 1344 } 1345 if (plans.has(uri)) { 1346 return (T) plans.get(uri, version); 1347 } 1348 if (maps.has(uri)) { 1349 return (T) maps.get(uri, version); 1350 } 1351 if (transforms.has(uri)) { 1352 return (T) transforms.get(uri, version); 1353 } 1354 if (questionnaires.has(uri)) { 1355 return (T) questionnaires.get(uri, version); 1356 } 1357 if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) { 1358 return null; 1359 } 1360 1361 // it might be a special URL. 1362 if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) { 1363 Resource res = null; // findTxValueSet(uri); 1364 if (res != null) { 1365 return (T) res; 1366 } 1367 } 1368 for (Map<String, ResourceProxy> rt : allResourcesById.values()) { 1369 for (ResourceProxy r : rt.values()) { 1370 if (uri.equals(r.getUrl())) { 1371 if (version == null || version == r.getResource().getMeta().getVersionId()) { 1372 return (T) r.getResource(); 1373 } 1374 } 1375 } 1376 } 1377 return null; 1378 } else if (class_ == ImplementationGuide.class) { 1379 return (T) guides.get(uri, version); 1380 } else if (class_ == CapabilityStatement.class) { 1381 return (T) capstmts.get(uri, version); 1382 } else if (class_ == Measure.class) { 1383 return (T) measures.get(uri, version); 1384 } else if (class_ == Library.class) { 1385 return (T) libraries.get(uri, version); 1386 } else if (class_ == StructureDefinition.class) { 1387 return (T) structures.get(uri, version); 1388 } else if (class_ == StructureMap.class) { 1389 return (T) transforms.get(uri, version); 1390 } else if (class_ == ValueSet.class) { 1391 return (T) valueSets.get(uri, version); 1392 } else if (class_ == CodeSystem.class) { 1393 return (T) codeSystems.get(uri, version); 1394 } else if (class_ == ConceptMap.class) { 1395 return (T) maps.get(uri, version); 1396 } else if (class_ == PlanDefinition.class) { 1397 return (T) plans.get(uri, version); 1398 } else if (class_ == OperationDefinition.class) { 1399 OperationDefinition od = operations.get(uri, version); 1400 return (T) od; 1401 } else if (class_ == Questionnaire.class) { 1402 return (T) questionnaires.get(uri, version); 1403 } else if (class_ == SearchParameter.class) { 1404 SearchParameter res = searchParameters.get(uri, version); 1405 return (T) res; 1406 } 1407 if (class_ == CodeSystem.class && codeSystems.has(uri)) { 1408 return (T) codeSystems.get(uri, version); 1409 } 1410 if (class_ == ValueSet.class && valueSets.has(uri)) { 1411 return (T) valueSets.get(uri, version); 1412 } 1413 1414 if (class_ == Questionnaire.class) { 1415 return (T) questionnaires.get(uri, version); 1416 } 1417 if (supportedCodeSystems.contains(uri)) { 1418 return null; 1419 } 1420 throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri)); 1421 } 1422 } 1423 1424 public PackageVersion getPackageForUrl(String uri) { 1425 if (uri == null) { 1426 return null; 1427 } 1428 uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs()); 1429 1430 synchronized (lock) { 1431 1432 String version = null; 1433 if (uri.contains("|")) { 1434 version = uri.substring(uri.lastIndexOf("|")+1); 1435 uri = uri.substring(0, uri.lastIndexOf("|")); 1436 } 1437 if (uri.contains("#")) { 1438 uri = uri.substring(0, uri.indexOf("#")); 1439 } 1440 if (structures.has(uri)) { 1441 return structures.getPackageInfo(uri, version); 1442 } 1443 if (guides.has(uri)) { 1444 return guides.getPackageInfo(uri, version); 1445 } 1446 if (capstmts.has(uri)) { 1447 return capstmts.getPackageInfo(uri, version); 1448 } 1449 if (measures.has(uri)) { 1450 return measures.getPackageInfo(uri, version); 1451 } 1452 if (libraries.has(uri)) { 1453 return libraries.getPackageInfo(uri, version); 1454 } 1455 if (valueSets.has(uri)) { 1456 return valueSets.getPackageInfo(uri, version); 1457 } 1458 if (codeSystems.has(uri)) { 1459 return codeSystems.getPackageInfo(uri, version); 1460 } 1461 if (operations.has(uri)) { 1462 return operations.getPackageInfo(uri, version); 1463 } 1464 if (searchParameters.has(uri)) { 1465 return searchParameters.getPackageInfo(uri, version); 1466 } 1467 if (plans.has(uri)) { 1468 return plans.getPackageInfo(uri, version); 1469 } 1470 if (maps.has(uri)) { 1471 return maps.getPackageInfo(uri, version); 1472 } 1473 if (transforms.has(uri)) { 1474 return transforms.getPackageInfo(uri, version); 1475 } 1476 if (questionnaires.has(uri)) { 1477 return questionnaires.getPackageInfo(uri, version); 1478 } 1479 return null; 1480 } 1481 } 1482 1483 @SuppressWarnings("unchecked") 1484 public <T extends Resource> T fetchResourceWithException(String cls, String uri, CanonicalResource source) throws FHIRException { 1485 if (uri == null) { 1486 return null; 1487 } 1488 1489 if ("StructureDefinition".equals(cls)) { 1490 uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs()); 1491 } 1492 synchronized (lock) { 1493 1494 String version = null; 1495 if (uri.contains("|")) { 1496 version = uri.substring(uri.lastIndexOf("|")+1); 1497 uri = uri.substring(0, uri.lastIndexOf("|")); 1498 } 1499 if (uri.contains("#")) { 1500 uri = uri.substring(0, uri.indexOf("#")); 1501 } 1502 if (cls == null || "Resource".equals(cls)) { 1503 if (structures.has(uri)) { 1504 return (T) structures.get(uri, version); 1505 } 1506 if (guides.has(uri)) { 1507 return (T) guides.get(uri, version); 1508 } 1509 if (capstmts.has(uri)) { 1510 return (T) capstmts.get(uri, version); 1511 } 1512 if (measures.has(uri)) { 1513 return (T) measures.get(uri, version); 1514 } 1515 if (libraries.has(uri)) { 1516 return (T) libraries.get(uri, version); 1517 } 1518 if (valueSets.has(uri)) { 1519 return (T) valueSets.get(uri, version); 1520 } 1521 if (codeSystems.has(uri)) { 1522 return (T) codeSystems.get(uri, version); 1523 } 1524 if (operations.has(uri)) { 1525 return (T) operations.get(uri, version); 1526 } 1527 if (searchParameters.has(uri)) { 1528 return (T) searchParameters.get(uri, version); 1529 } 1530 if (plans.has(uri)) { 1531 return (T) plans.get(uri, version); 1532 } 1533 if (maps.has(uri)) { 1534 return (T) maps.get(uri, version); 1535 } 1536 if (transforms.has(uri)) { 1537 return (T) transforms.get(uri, version); 1538 } 1539 if (questionnaires.has(uri)) { 1540 return (T) questionnaires.get(uri, version); 1541 } 1542 for (Map<String, ResourceProxy> rt : allResourcesById.values()) { 1543 for (ResourceProxy r : rt.values()) { 1544 if (uri.equals(r.getUrl())) { 1545 return (T) r.getResource(); 1546 } 1547 } 1548 } 1549 } else if ("ImplementationGuide".equals(cls)) { 1550 return (T) guides.get(uri, version); 1551 } else if ("CapabilityStatement".equals(cls)) { 1552 return (T) capstmts.get(uri, version); 1553 } else if ("Measure".equals(cls)) { 1554 return (T) measures.get(uri, version); 1555 } else if ("Library".equals(cls)) { 1556 return (T) libraries.get(uri, version); 1557 } else if ("StructureDefinition".equals(cls)) { 1558 return (T) structures.get(uri, version); 1559 } else if ("StructureMap".equals(cls)) { 1560 return (T) transforms.get(uri, version); 1561 } else if ("ValueSet".equals(cls)) { 1562 return (T) valueSets.get(uri, version); 1563 } else if ("CodeSystem".equals(cls)) { 1564 return (T) codeSystems.get(uri, version); 1565 } else if ("ConceptMap".equals(cls)) { 1566 return (T) maps.get(uri, version); 1567 } else if ("PlanDefinition".equals(cls)) { 1568 return (T) plans.get(uri, version); 1569 } else if ("OperationDefinition".equals(cls)) { 1570 OperationDefinition od = operations.get(uri, version); 1571 return (T) od; 1572 } else if ("Questionnaire.class".equals(cls)) { 1573 return (T) questionnaires.get(uri, version); 1574 } else if ("SearchParameter.class".equals(cls)) { 1575 SearchParameter res = searchParameters.get(uri, version); 1576 return (T) res; 1577 } 1578 if ("CodeSystem".equals(cls) && codeSystems.has(uri)) { 1579 return (T) codeSystems.get(uri, version); 1580 } 1581 if ("ValueSet".equals(cls) && valueSets.has(uri)) { 1582 return (T) valueSets.get(uri, version); 1583 } 1584 1585 if ("Questionnaire".equals(cls)) { 1586 return (T) questionnaires.get(uri, version); 1587 } 1588 if (cls == null) { 1589 if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) { 1590 return null; 1591 } 1592 1593 // it might be a special URL. 1594 if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) { 1595 Resource res = null; // findTxValueSet(uri); 1596 if (res != null) { 1597 return (T) res; 1598 } 1599 } 1600 return null; 1601 } 1602 if (supportedCodeSystems.contains(uri)) { 1603 return null; 1604 } 1605 throw new FHIRException(formatMessage(I18nConstants.NOT_DONE_YET_CANT_FETCH_, uri)); 1606 } 1607 } 1608 1609 private Set<String> notCanonical = new HashSet<String>(); 1610 1611 private String overrideVersionNs; 1612 1613 @Override 1614 public Resource fetchResourceById(String type, String uri) { 1615 synchronized (lock) { 1616 String[] parts = uri.split("\\/"); 1617 if (!Utilities.noString(type) && parts.length == 1) { 1618 if (allResourcesById.containsKey(type)) { 1619 return allResourcesById.get(type).get(parts[0]).getResource(); 1620 } else { 1621 return null; 1622 } 1623 } 1624 if (parts.length >= 2) { 1625 if (!Utilities.noString(type)) { 1626 if (!type.equals(parts[parts.length-2])) { 1627 throw new Error(formatMessage(I18nConstants.RESOURCE_TYPE_MISMATCH_FOR___, type, uri)); 1628 } 1629 } 1630 return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]).getResource(); 1631 } else { 1632 throw new Error(formatMessage(I18nConstants.UNABLE_TO_PROCESS_REQUEST_FOR_RESOURCE_FOR___, type, uri)); 1633 } 1634 } 1635 } 1636 1637 public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource source) { 1638 try { 1639 return fetchResourceWithException(class_, uri, source); 1640 } catch (FHIRException e) { 1641 throw new Error(e); 1642 } 1643 } 1644 1645 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 1646 try { 1647 return fetchResourceWithException(class_, uri, null); 1648 } catch (FHIRException e) { 1649 throw new Error(e); 1650 } 1651 } 1652 1653 public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) { 1654 try { 1655 return fetchResourceWithException(class_, uri, version, null); 1656 } catch (FHIRException e) { 1657 throw new Error(e); 1658 } 1659 } 1660 1661 @Override 1662 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 1663 try { 1664 return fetchResourceWithException(class_, uri) != null; 1665 } catch (Exception e) { 1666 return false; 1667 } 1668 } 1669 1670 public <T extends Resource> boolean hasResource(String cls, String uri) { 1671 try { 1672 return fetchResourceWithException(cls, uri) != null; 1673 } catch (Exception e) { 1674 return false; 1675 } 1676 } 1677 1678 1679 public TranslationServices translator() { 1680 return translator; 1681 } 1682 1683 public void setTranslator(TranslationServices translator) { 1684 this.translator = translator; 1685 } 1686 1687 public class NullTranslator implements TranslationServices { 1688 1689 @Override 1690 public String translate(String context, String value, String targetLang) { 1691 return value; 1692 } 1693 1694 @Override 1695 public String translate(String context, String value) { 1696 return value; 1697 } 1698 1699 @Override 1700 public String toStr(float value) { 1701 return null; 1702 } 1703 1704 @Override 1705 public String toStr(Date value) { 1706 return null; 1707 } 1708 1709 @Override 1710 public String translateAndFormat(String contest, String lang, String value, Object... args) { 1711 return String.format(value, args); 1712 } 1713 1714 @Override 1715 public Map<String, String> translations(String value) { 1716 // TODO Auto-generated method stub 1717 return null; 1718 } 1719 1720 @Override 1721 public Set<String> listTranslations(String category) { 1722 // TODO Auto-generated method stub 1723 return null; 1724 } 1725 1726 } 1727 1728 public void reportStatus(JsonObject json) { 1729 synchronized (lock) { 1730 json.addProperty("codeystem-count", codeSystems.size()); 1731 json.addProperty("valueset-count", valueSets.size()); 1732 json.addProperty("conceptmap-count", maps.size()); 1733 json.addProperty("transforms-count", transforms.size()); 1734 json.addProperty("structures-count", structures.size()); 1735 json.addProperty("guides-count", guides.size()); 1736 json.addProperty("statements-count", capstmts.size()); 1737 json.addProperty("measures-count", measures.size()); 1738 json.addProperty("libraries-count", libraries.size()); 1739 } 1740 } 1741 1742 1743 public void dropResource(Resource r) throws FHIRException { 1744 dropResource(r.fhirType(), r.getId()); 1745 } 1746 1747 public void dropResource(String fhirType, String id) { 1748 synchronized (lock) { 1749 1750 Map<String, ResourceProxy> map = allResourcesById.get(fhirType); 1751 if (map == null) { 1752 map = new HashMap<String, ResourceProxy>(); 1753 allResourcesById.put(fhirType, map); 1754 } 1755 if (map.containsKey(id)) { 1756 map.remove(id); // this is a challenge because we might have more than one resource with this id (different versions) 1757 } 1758 1759 if (fhirType.equals("StructureDefinition")) { 1760 structures.drop(id); 1761 } else if (fhirType.equals("ImplementationGuide")) { 1762 guides.drop(id); 1763 } else if (fhirType.equals("CapabilityStatement")) { 1764 capstmts.drop(id); 1765 } else if (fhirType.equals("Measure")) { 1766 measures.drop(id); 1767 } else if (fhirType.equals("Library")) { 1768 libraries.drop(id); 1769 } else if (fhirType.equals("ValueSet")) { 1770 valueSets.drop(id); 1771 } else if (fhirType.equals("CodeSystem")) { 1772 codeSystems.drop(id); 1773 } else if (fhirType.equals("OperationDefinition")) { 1774 operations.drop(id); 1775 } else if (fhirType.equals("Questionnaire")) { 1776 questionnaires.drop(id); 1777 } else if (fhirType.equals("ConceptMap")) { 1778 maps.drop(id); 1779 } else if (fhirType.equals("StructureMap")) { 1780 transforms.drop(id); 1781 } else if (fhirType.equals("NamingSystem")) { 1782 systems.drop(id); 1783 } 1784 } 1785 } 1786 1787 private <T extends CanonicalResource> void dropMetadataResource(Map<String, T> map, String id) { 1788 T res = map.get(id); 1789 if (res != null) { 1790 map.remove(id); 1791 if (map.containsKey(res.getUrl())) { 1792 map.remove(res.getUrl()); 1793 } 1794 if (res.getVersion() != null) { 1795 if (map.containsKey(res.getUrl()+"|"+res.getVersion())) { 1796 map.remove(res.getUrl()+"|"+res.getVersion()); 1797 } 1798 } 1799 } 1800 } 1801 1802 @Override 1803 public List<CanonicalResource> allConformanceResources() { 1804 synchronized (lock) { 1805 List<CanonicalResource> result = new ArrayList<CanonicalResource>(); 1806 structures.listAllM(result); 1807 guides.listAllM(result); 1808 capstmts.listAllM(result); 1809 measures.listAllM(result); 1810 libraries.listAllM(result); 1811 codeSystems.listAllM(result); 1812 valueSets.listAllM(result); 1813 maps.listAllM(result); 1814 transforms.listAllM(result); 1815 plans.listAllM(result); 1816 questionnaires.listAllM(result); 1817 systems.listAllM(result); 1818 return result; 1819 } 1820 } 1821 1822 public String listSupportedSystems() { 1823 synchronized (lock) { 1824 String sl = null; 1825 for (String s : supportedCodeSystems) { 1826 sl = sl == null ? s : sl + "\r\n" + s; 1827 } 1828 return sl; 1829 } 1830 } 1831 1832 1833 public int totalCount() { 1834 synchronized (lock) { 1835 return valueSets.size() + maps.size() + structures.size() + transforms.size(); 1836 } 1837 } 1838 1839 public List<ConceptMap> listMaps() { 1840 List<ConceptMap> m = new ArrayList<ConceptMap>(); 1841 synchronized (lock) { 1842 maps.listAll(m); 1843 } 1844 return m; 1845 } 1846 1847 public List<StructureMap> listTransforms() { 1848 List<StructureMap> m = new ArrayList<StructureMap>(); 1849 synchronized (lock) { 1850 transforms.listAll(m); 1851 } 1852 return m; 1853 } 1854 1855 public StructureMap getTransform(String code) { 1856 synchronized (lock) { 1857 return transforms.get(code); 1858 } 1859 } 1860 1861 public List<StructureDefinition> listStructures() { 1862 List<StructureDefinition> m = new ArrayList<StructureDefinition>(); 1863 synchronized (lock) { 1864 structures.listAll(m); 1865 } 1866 return m; 1867 } 1868 1869 public StructureDefinition getStructure(String code) { 1870 synchronized (lock) { 1871 return structures.get(code); 1872 } 1873 } 1874 1875 @Override 1876 public String oid2Uri(String oid) { 1877 synchronized (lock) { 1878 if (oid != null && oid.startsWith("urn:oid:")) { 1879 oid = oid.substring(8); 1880 } 1881 if (oidCache.containsKey(oid)) { 1882 return oidCache.get(oid); 1883 } 1884 1885 String uri = OIDUtils.getUriForOid(oid); 1886 if (uri != null) { 1887 oidCache.put(oid, uri); 1888 return uri; 1889 } 1890 CodeSystem cs = fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables"); 1891 if (cs != null) { 1892 for (ConceptDefinitionComponent cc : cs.getConcept()) { 1893 for (ConceptPropertyComponent cp : cc.getProperty()) { 1894 if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) { 1895 for (ConceptPropertyComponent cp2 : cc.getProperty()) { 1896 if ("v2-cs-uri".equals(cp2.getCode())) { 1897 oidCache.put(oid, cp2.getValue().primitiveValue()); 1898 return cp2.getValue().primitiveValue(); 1899 } 1900 } 1901 } 1902 } 1903 } 1904 } 1905 for (CodeSystem css : codeSystems.getList()) { 1906 if (("urn:oid:"+oid).equals(css.getUrl())) { 1907 oidCache.put(oid, css.getUrl()); 1908 return css.getUrl(); 1909 } 1910 for (Identifier id : css.getIdentifier()) { 1911 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) { 1912 oidCache.put(oid, css.getUrl()); 1913 return css.getUrl(); 1914 } 1915 } 1916 } 1917 for (NamingSystem ns : systems.getList()) { 1918 if (hasOid(ns, oid)) { 1919 uri = getUri(ns); 1920 if (uri != null) { 1921 oidCache.put(oid, null); 1922 return null; 1923 } 1924 } 1925 } 1926 } 1927 oidCache.put(oid, null); 1928 return null; 1929 } 1930 1931 1932 private String getUri(NamingSystem ns) { 1933 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 1934 if (id.getType() == NamingSystemIdentifierType.URI) { 1935 return id.getValue(); 1936 } 1937 } 1938 return null; 1939 } 1940 1941 private boolean hasOid(NamingSystem ns, String oid) { 1942 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 1943 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) { 1944 return true; 1945 } 1946 } 1947 return false; 1948 } 1949 1950 public void cacheVS(JsonObject json, Map<String, ValidationResult> t) { 1951 synchronized (lock) { 1952 validationCache.put(json.get("url").getAsString(), t); 1953 } 1954 } 1955 1956 public SearchParameter getSearchParameter(String code) { 1957 synchronized (lock) { 1958 return searchParameters.get(code); 1959 } 1960 } 1961 1962 @Override 1963 public String getOverrideVersionNs() { 1964 return overrideVersionNs; 1965 } 1966 1967 @Override 1968 public void setOverrideVersionNs(String value) { 1969 overrideVersionNs = value; 1970 } 1971 1972 @Override 1973 public ILoggingService getLogger() { 1974 return logger; 1975 } 1976 1977 @Override 1978 public StructureDefinition fetchTypeDefinition(String typeName) { 1979 if (Utilities.isAbsoluteUrl(typeName)) { 1980 return fetchResource(StructureDefinition.class, typeName); 1981 } else { 1982 return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+typeName); 1983 } 1984 } 1985 1986 public boolean isTlogging() { 1987 return tlogging; 1988 } 1989 1990 public void setTlogging(boolean tlogging) { 1991 this.tlogging = tlogging; 1992 } 1993 1994 public UcumService getUcumService() { 1995 return ucumService; 1996 } 1997 1998 public void setUcumService(UcumService ucumService) { 1999 this.ucumService = ucumService; 2000 } 2001 2002 @Override 2003 public List<StructureDefinition> getStructures() { 2004 List<StructureDefinition> res = new ArrayList<>(); 2005 synchronized (lock) { // tricky, because you need to lock this as well, but it's really not in use yet 2006 structures.listAll(res); 2007 } 2008 return res; 2009 } 2010 2011 public String getLinkForUrl(String corePath, String url) { 2012 if (url == null) { 2013 return null; 2014 } 2015 2016 if (codeSystems.has(url)) { 2017 return codeSystems.get(url).getUserString("path"); 2018 } 2019 2020 if (valueSets.has(url)) { 2021 return valueSets.get(url).getUserString("path"); 2022 } 2023 2024 if (maps.has(url)) { 2025 return maps.get(url).getUserString("path"); 2026 } 2027 2028 if (transforms.has(url)) { 2029 return transforms.get(url).getUserString("path"); 2030 } 2031 2032 if (structures.has(url)) { 2033 return structures.get(url).getUserString("path"); 2034 } 2035 2036 if (guides.has(url)) { 2037 return guides.get(url).getUserString("path"); 2038 } 2039 2040 if (capstmts.has(url)) { 2041 return capstmts.get(url).getUserString("path"); 2042 } 2043 2044 if (measures.has(url)) { 2045 return measures.get(url).getUserString("path"); 2046 } 2047 2048 if (libraries.has(url)) { 2049 return libraries.get(url).getUserString("path"); 2050 } 2051 2052 if (searchParameters.has(url)) { 2053 return searchParameters.get(url).getUserString("path"); 2054 } 2055 2056 if (questionnaires.has(url)) { 2057 return questionnaires.get(url).getUserString("path"); 2058 } 2059 2060 if (operations.has(url)) { 2061 return operations.get(url).getUserString("path"); 2062 } 2063 2064 if (plans.has(url)) { 2065 return plans.get(url).getUserString("path"); 2066 } 2067 2068 if (url.equals("http://loinc.org")) { 2069 return corePath+"loinc.html"; 2070 } 2071 if (url.equals("http://unitsofmeasure.org")) { 2072 return corePath+"ucum.html"; 2073 } 2074 if (url.equals("http://snomed.info/sct")) { 2075 return corePath+"snomed.html"; 2076 } 2077 return null; 2078 } 2079 2080 public List<ImplementationGuide> allImplementationGuides() { 2081 List<ImplementationGuide> res = new ArrayList<>(); 2082 guides.listAll(res); 2083 return res; 2084 } 2085 2086 @Override 2087 public Map<String, byte[]> getBinaries() { 2088 return binaries; 2089 } 2090 2091 public void finishLoading() { 2092 for (StructureDefinition sd : listStructures()) { 2093 try { 2094 if (sd.getSnapshot().isEmpty()) { 2095 generateSnapshot(sd); 2096// new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd); 2097 } 2098 } catch (Exception e) { 2099// System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage()); 2100 } 2101 } 2102 } 2103 2104 protected String tail(String url) { 2105 if (Utilities.noString(url)) { 2106 return "noname"; 2107 } 2108 if (url.contains("/")) { 2109 return url.substring(url.lastIndexOf("/")+1); 2110 } 2111 return url; 2112 } 2113 2114 public int getClientRetryCount() { 2115 return txClient == null ? 0 : txClient.getRetryCount(); 2116 } 2117 2118 public IWorkerContext setClientRetryCount(int value) { 2119 if (txClient != null) { 2120 txClient.setRetryCount(value); 2121 } 2122 return this; 2123 } 2124 2125 public String getTxCache() { 2126 return txCache.getFolder(); 2127 } 2128 2129 public TerminologyClient getTxClient() { 2130 return txClient; 2131 } 2132 2133 public String getCacheId() { 2134 return cacheId; 2135 } 2136 2137 public void setCacheId(String cacheId) { 2138 this.cacheId = cacheId; 2139 } 2140 2141 public TerminologyCapabilities getTxCaps() { 2142 return txcaps; 2143 } 2144 2145 public void setTxCaps(TerminologyCapabilities txCaps) { 2146 this.txcaps = txCaps; 2147 if (txCaps != null) { 2148 for (TerminologyCapabilitiesExpansionParameterComponent t : txcaps.getExpansion().getParameter()) { 2149 if ("cache-id".equals(t.getName())) { 2150 isTxCaching = true; 2151 } 2152 } 2153 for (TerminologyCapabilitiesCodeSystemComponent tccs : txcaps.getCodeSystem()) { 2154 supportedCodeSystems.add(tccs.getUri()); 2155 } 2156 } 2157 } 2158 2159 public TimeTracker clock() { 2160 return clock; 2161 } 2162 2163 2164 public int countAllCaches() { 2165 return codeSystems.size() + valueSets.size() + maps.size() + transforms.size() + structures.size() + measures.size() + libraries.size() + 2166 guides.size() + capstmts.size() + searchParameters.size() + questionnaires.size() + operations.size() + plans.size() + systems.size(); 2167 } 2168 2169 public Set<String> getCodeSystemsUsed() { 2170 return codeSystemsUsed ; 2171 } 2172 2173 public String getSpecUrl() { 2174 String v = getVersion(); 2175 switch (VersionUtilities.getMajMin(v)) { 2176 case "1.0" : return "http://hl7.org/fhir/DSTU1"; 2177 case "1.4" : return "http://hl7.org/fhir/DSTU2"; 2178 case "3.0" : return "http://hl7.org/fhir/STU3"; 2179 case "4.0" : return "http://hl7.org/fhir/R4"; 2180 case "4.5" : return "http://build.fhir.org"; 2181 case "5.0" : return "http://build.fhir.org"; 2182 default: 2183 return "http://hl7.org/fhir"; 2184 } 2185 } 2186 2187 public ICanonicalResourceLocator getLocator() { 2188 return locator; 2189 } 2190 2191 public void setLocator(ICanonicalResourceLocator locator) { 2192 this.locator = locator; 2193 } 2194 2195 public String getUserAgent() { 2196 return userAgent; 2197 } 2198 2199 public void setUserAgent(String userAgent) { 2200 this.userAgent = userAgent; 2201 if (txClient != null) 2202 txClient.setUserAgent(userAgent); 2203 } 2204 2205}