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