001package org.hl7.fhir.r4.context; 002 003import java.io.ByteArrayOutputStream; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.FileNotFoundException; 007import java.io.FileOutputStream; 008import java.io.IOException; 009import java.util.*; 010 011import org.apache.commons.codec.Charsets; 012import org.apache.commons.lang3.StringUtils; 013import org.hl7.fhir.r4.formats.IParser.OutputStyle; 014import org.hl7.fhir.r4.conformance.ProfileUtilities; 015import org.hl7.fhir.r4.context.BaseWorkerContext.NullTranslator; 016import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 017import org.hl7.fhir.r4.context.IWorkerContext.ILoggingService.LogCategory; 018import org.hl7.fhir.r4.context.TerminologyCache.CacheToken; 019import org.hl7.fhir.r4.formats.JsonParser; 020import org.hl7.fhir.r4.model.BooleanType; 021import org.hl7.fhir.r4.model.Bundle; 022import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; 023import org.hl7.fhir.r4.model.CodeSystem; 024import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; 025import org.hl7.fhir.r4.model.CodeSystem.CodeSystemHierarchyMeaning; 026import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 027import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionDesignationComponent; 028import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 029import org.hl7.fhir.r4.model.NamingSystem.NamingSystemIdentifierType; 030import org.hl7.fhir.r4.model.NamingSystem.NamingSystemUniqueIdComponent; 031import org.hl7.fhir.r4.model.CodeableConcept; 032import org.hl7.fhir.r4.model.Coding; 033import org.hl7.fhir.r4.model.ConceptMap; 034import org.hl7.fhir.r4.model.Constants; 035import org.hl7.fhir.r4.model.ImplementationGuide; 036import org.hl7.fhir.r4.model.MetadataResource; 037import org.hl7.fhir.r4.model.NamingSystem; 038import org.hl7.fhir.r4.model.OperationDefinition; 039import org.hl7.fhir.r4.model.OperationOutcome; 040import org.hl7.fhir.r4.model.Parameters; 041import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; 042import org.hl7.fhir.r4.model.PlanDefinition; 043import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 044import org.hl7.fhir.r4.model.PrimitiveType; 045import org.hl7.fhir.r4.model.Questionnaire; 046import org.hl7.fhir.r4.model.Reference; 047import org.hl7.fhir.r4.model.Resource; 048import org.hl7.fhir.r4.model.SearchParameter; 049import org.hl7.fhir.r4.model.StringType; 050import org.hl7.fhir.r4.model.StructureDefinition; 051import org.hl7.fhir.r4.model.StructureMap; 052import org.hl7.fhir.r4.model.UriType; 053import org.hl7.fhir.r4.model.ValueSet; 054import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; 055import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; 056import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent; 057import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 058import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 059import org.hl7.fhir.r4.terminologies.ValueSetCheckerSimple; 060import org.hl7.fhir.r4.terminologies.ValueSetExpander.ETooCostly; 061import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 062import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 063import org.hl7.fhir.r4.terminologies.ValueSetExpanderFactory; 064import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple; 065import org.hl7.fhir.r4.terminologies.ValueSetExpansionCache; 066import org.hl7.fhir.r4.utils.ToolingExtensions; 067import org.hl7.fhir.r4.utils.client.FHIRToolingClient; 068import org.hl7.fhir.exceptions.DefinitionException; 069import org.hl7.fhir.exceptions.FHIRException; 070import org.hl7.fhir.exceptions.NoTerminologyServiceException; 071import org.hl7.fhir.exceptions.TerminologyServiceException; 072import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 073import org.hl7.fhir.utilities.MimeType; 074import org.hl7.fhir.utilities.OIDUtils; 075import org.hl7.fhir.utilities.TextFile; 076import org.hl7.fhir.utilities.TranslationServices; 077import org.hl7.fhir.utilities.Utilities; 078import org.hl7.fhir.utilities.validation.ValidationMessage; 079import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 080import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 081import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 082 083import com.google.gson.JsonObject; 084import com.google.gson.JsonSyntaxException; 085 086import ca.uhn.fhir.rest.annotation.Metadata; 087 088public abstract class BaseWorkerContext implements IWorkerContext { 089 090 private Object lock = new Object(); // used as a lock for the data that follows 091 092 private Map<String, Map<String, Resource>> allResourcesById = new HashMap<String, Map<String, Resource>>(); 093 // all maps are to the full URI 094 private Map<String, CodeSystem> codeSystems = new HashMap<String, CodeSystem>(); 095 private Set<String> nonSupportedCodeSystems = new HashSet<String>(); 096 private Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>(); 097 private Map<String, ConceptMap> maps = new HashMap<String, ConceptMap>(); 098 private Map<String, StructureMap> transforms = new HashMap<String, StructureMap>(); 099 private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>(); 100 private Map<String, ImplementationGuide> guides = new HashMap<String, ImplementationGuide>(); 101 private Map<String, SearchParameter> searchParameters = new HashMap<String, SearchParameter>(); 102 private Map<String, Questionnaire> questionnaires = new HashMap<String, Questionnaire>(); 103 private Map<String, OperationDefinition> operations = new HashMap<String, OperationDefinition>(); 104 private Map<String, PlanDefinition> plans = new HashMap<String, PlanDefinition>(); 105 private List<NamingSystem> systems = new ArrayList<NamingSystem>(); 106 107 protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String,ValidationResult>>(); 108 protected String tsServer; 109 protected String name; 110 private boolean allowLoadingDuplicates; 111 112 protected FHIRToolingClient txServer; 113 private Bundle bndCodeSystems; 114 private boolean canRunWithoutTerminology; 115 protected boolean noTerminologyServer; 116 private int expandCodesLimit = 1000; 117 protected ILoggingService logger; 118 protected Parameters expParameters; 119 private TranslationServices translator = new NullTranslator(); 120 protected TerminologyCache txCache; 121 122 public BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException { 123 super(); 124 txCache = new TerminologyCache(lock, null); 125 } 126 127 public BaseWorkerContext(Map<String, CodeSystem> codeSystems, Map<String, ValueSet> valueSets, Map<String, ConceptMap> maps, Map<String, StructureDefinition> profiles, Map<String, ImplementationGuide> guides) throws FileNotFoundException, IOException, FHIRException { 128 super(); 129 this.codeSystems = codeSystems; 130 this.valueSets = valueSets; 131 this.maps = maps; 132 this.structures = profiles; 133 this.guides = guides; 134 txCache = new TerminologyCache(lock, null); 135 } 136 137 protected void copy(BaseWorkerContext other) { 138 synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet 139 allResourcesById.putAll(other.allResourcesById); 140 translator = other.translator; 141 codeSystems.putAll(other.codeSystems); 142 nonSupportedCodeSystems.addAll(other.nonSupportedCodeSystems); 143 valueSets.putAll(other.valueSets); 144 maps.putAll(other.maps); 145 transforms.putAll(other.transforms); 146 structures.putAll(other.structures); 147 searchParameters.putAll(other.searchParameters); 148 plans.putAll(other.plans); 149 questionnaires.putAll(other.questionnaires); 150 operations.putAll(other.operations); 151 systems.addAll(other.systems); 152 guides.putAll(other.guides); 153 154 allowLoadingDuplicates = other.allowLoadingDuplicates; 155 tsServer = other.tsServer; 156 name = other.name; 157 txServer = other.txServer; 158 bndCodeSystems = other.bndCodeSystems; 159 canRunWithoutTerminology = other.canRunWithoutTerminology; 160 noTerminologyServer = other.noTerminologyServer; 161 if (other.txCache != null) 162 txCache = other.txCache.copy(); 163 expandCodesLimit = other.expandCodesLimit; 164 logger = other.logger; 165 expParameters = other.expParameters; 166 } 167 } 168 169 public void cacheResource(Resource r) throws FHIRException { 170 synchronized (lock) { 171 Map<String, Resource> map = allResourcesById.get(r.fhirType()); 172 if (map == null) { 173 map = new HashMap<String, Resource>(); 174 allResourcesById.put(r.fhirType(), map); 175 } 176 map.put(r.getId(), r); 177 178 if (r instanceof MetadataResource) { 179 MetadataResource m = (MetadataResource) r; 180 String url = m.getUrl(); 181 if (!allowLoadingDuplicates && hasResource(r.getClass(), url)) 182 throw new DefinitionException("Duplicate Resource " + url); 183 if (r instanceof StructureDefinition) 184 seeMetadataResource((StructureDefinition) m, structures, false); 185 else if (r instanceof ValueSet) 186 seeMetadataResource((ValueSet) m, valueSets, false); 187 else if (r instanceof CodeSystem) 188 seeMetadataResource((CodeSystem) m, codeSystems, false); 189 else if (r instanceof ImplementationGuide) 190 seeMetadataResource((ImplementationGuide) m, guides, false); 191 else if (r instanceof SearchParameter) 192 seeMetadataResource((SearchParameter) m, searchParameters, false); 193 else if (r instanceof PlanDefinition) 194 seeMetadataResource((PlanDefinition) m, plans, false); 195 else if (r instanceof OperationDefinition) 196 seeMetadataResource((OperationDefinition) m, operations, false); 197 else if (r instanceof Questionnaire) 198 seeMetadataResource((Questionnaire) m, questionnaires, true); 199 else if (r instanceof ConceptMap) 200 seeMetadataResource((ConceptMap) m, maps, false); 201 else if (r instanceof StructureMap) 202 seeMetadataResource((StructureMap) m, transforms, false); 203 else if (r instanceof NamingSystem) 204 systems.add((NamingSystem) r); 205 } 206 } 207 } 208 209 /* 210 * Compare business versions, returning "true" if the candidate newer version is in fact newer than the oldVersion 211 * Comparison will work for strictly numeric versions as well as multi-level versions separated by ., -, _, : or space 212 * Failing that, it will do unicode-based character ordering. 213 * E.g. 1.5.3 < 1.14.3 214 * 2017-3-10 < 2017-12-7 215 * A3 < T2 216 */ 217 private boolean laterVersion(String newVersion, String oldVersion) { 218 // Compare business versions, retur 219 newVersion = newVersion.trim(); 220 oldVersion = oldVersion.trim(); 221 if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion)) 222 return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion); 223 else if (hasDelimiter(newVersion, oldVersion, ".")) 224 return laterDelimitedVersion(newVersion, oldVersion, "\\."); 225 else if (hasDelimiter(newVersion, oldVersion, "-")) 226 return laterDelimitedVersion(newVersion, oldVersion, "\\-"); 227 else if (hasDelimiter(newVersion, oldVersion, "_")) 228 return laterDelimitedVersion(newVersion, oldVersion, "\\_"); 229 else if (hasDelimiter(newVersion, oldVersion, ":")) 230 return laterDelimitedVersion(newVersion, oldVersion, "\\:"); 231 else if (hasDelimiter(newVersion, oldVersion, " ")) 232 return laterDelimitedVersion(newVersion, oldVersion, "\\ "); 233 else { 234 return newVersion.compareTo(oldVersion) > 0; 235 } 236 } 237 238 /* 239 * Returns true if both strings include the delimiter and have the same number of occurrences of it 240 */ 241 private boolean hasDelimiter(String s1, String s2, String delimiter) { 242 return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length; 243 } 244 245 private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) { 246 String[] newParts = newVersion.split(delimiter); 247 String[] oldParts = oldVersion.split(delimiter); 248 for (int i = 0; i < newParts.length; i++) { 249 if (!newParts[i].equals(oldParts[i])) 250 return laterVersion(newParts[i], oldParts[i]); 251 } 252 // This should never happen 253 throw new Error("Delimited versions have exact match for delimiter '"+delimiter+"' : "+ Arrays.asList(newParts)+" vs "+Arrays.asList(oldParts)); 254 } 255 256 protected <T extends MetadataResource> void seeMetadataResource(T r, Map<String, T> map, boolean addId) throws FHIRException { 257 if (addId) 258 map.put(r.getId(), r); // todo: why? 259 if (!map.containsKey(r.getUrl())) 260 map.put(r.getUrl(), r); 261 else { 262 // If this resource already exists, see if it's the newest business version. The default resource to return if not qualified is the most recent business version 263 MetadataResource existingResource = (MetadataResource)map.get(r.getUrl()); 264 if (r.hasVersion() && existingResource.hasVersion() && !r.getVersion().equals(existingResource.getVersion())) { 265 if (laterVersion(r.getVersion(), existingResource.getVersion())) { 266 map.remove(r.getUrl()); 267 map.put(r.getUrl(), r); 268 } 269 } else 270 map.remove(r.getUrl()); 271 map.put(r.getUrl(), r); 272// throw new FHIRException("Multiple declarations of resource with same canonical URL (" + r.getUrl() + ") and version (" + (r.hasVersion() ? r.getVersion() : "" ) + ")"); 273 } 274 if (r.hasVersion()) 275 map.put(r.getUrl()+"|"+r.getVersion(), r); 276 } 277 278 @Override 279 public CodeSystem fetchCodeSystem(String system) { 280 synchronized (lock) { 281 return codeSystems.get(system); 282 } 283 } 284 285 @Override 286 public boolean supportsSystem(String system) throws TerminologyServiceException { 287 synchronized (lock) { 288 if (codeSystems.containsKey(system)) 289 return true; 290 else if (nonSupportedCodeSystems.contains(system)) 291 return false; 292 else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) 293 return false; 294 else { 295 if (noTerminologyServer) 296 return false; 297 if (bndCodeSystems == null) { 298 try { 299 log("Terminology server: Check for supported code systems for "+system); 300 bndCodeSystems = txServer.fetchFeed(txServer.getAddress()+"/CodeSystem?content-mode=not-present&_summary=true&_count=1000"); 301 } catch (Exception e) { 302 if (canRunWithoutTerminology) { 303 noTerminologyServer = true; 304 log("==============!! Running without terminology server !! =============="); 305 if (txServer!=null) { 306 log("txServer = "+txServer.getAddress()); 307 log("Error = "+e.getMessage()+""); 308 } 309 log("====================================================================="); 310 return false; 311 } else 312 throw new TerminologyServiceException(e); 313 } 314 } 315 if (bndCodeSystems != null) { 316 for (BundleEntryComponent be : bndCodeSystems.getEntry()) { 317 CodeSystem cs = (CodeSystem) be.getResource(); 318// if (cs == null) 319// System.out.println("no resource for "+be.getFullUrl()); 320 if (cs != null && !codeSystems.containsKey(cs.getUrl())) { 321 codeSystems.put(cs.getUrl(), null); 322 } 323 } 324 } 325 if (codeSystems.containsKey(system)) 326 return true; 327 } 328 nonSupportedCodeSystems.add(system); 329 return false; 330 } 331 } 332 333 private void log(String message) { 334 if (logger != null) 335 logger.logMessage(message); 336 else 337 System.out.println(message); 338 } 339 340 341 protected void tlog(String msg) { 342 System.out.println("-tx cache miss: "+msg); 343 } 344 345 // --- expansion support ------------------------------------------------------------------------------------------------------------ 346 347 public int getExpandCodesLimit() { 348 return expandCodesLimit; 349 } 350 351 public void setExpandCodesLimit(int expandCodesLimit) { 352 this.expandCodesLimit = expandCodesLimit; 353 } 354 355 @Override 356 public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException { 357 ValueSet vs = null; 358 vs = fetchResource(ValueSet.class, binding.getValueSet()); 359 if (vs == null) 360 throw new FHIRException("Unable to resolve value Set "+binding.getValueSet()); 361 return expandVS(vs, cacheOk, heirarchical); 362 } 363 364 @Override 365 public ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean heirachical) throws TerminologyServiceException { 366 ValueSet vs = new ValueSet(); 367 vs.setCompose(new ValueSetComposeComponent()); 368 vs.getCompose().getInclude().add(inc); 369 CacheToken cacheToken = txCache.generateExpandToken(vs, heirachical); 370 ValueSetExpansionOutcome res; 371 res = txCache.getExpansion(cacheToken); 372 if (res != null) 373 return res; 374 Parameters p = expParameters.copy(); 375 p.setParameter("includeDefinition", false); 376 p.setParameter("excludeNested", !heirachical); 377 378 if (noTerminologyServer) 379 return new ValueSetExpansionOutcome("Error expanding ValueSet: running without terminology services", TerminologyServiceErrorClass.NOSERVICE); 380 Map<String, String> params = new HashMap<String, String>(); 381 params.put("_limit", Integer.toString(expandCodesLimit )); 382 params.put("_incomplete", "true"); 383 tlog("$expand on "+txCache.summary(vs)); 384 try { 385 ValueSet result = txServer.expandValueset(vs, p, params); 386 res = new ValueSetExpansionOutcome(result); 387 } catch (Exception e) { 388 res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); 389 } 390 txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); 391 return res; 392 393 394 395 } 396 397 @Override 398 public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) { 399 if (expParameters == null) 400 throw new Error("No Expansion Parameters provided"); 401 Parameters p = expParameters.copy(); 402 return expandVS(vs, cacheOk, heirarchical, p); 403 } 404 405 public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, Parameters p) { 406 if (p == null) 407 throw new Error("No Parameters provided to expandVS"); 408 if (vs.hasExpansion()) { 409 return new ValueSetExpansionOutcome(vs.copy()); 410 } 411 if (!vs.hasUrl()) 412 throw new Error("no value set"); 413 414 CacheToken cacheToken = txCache.generateExpandToken(vs, heirarchical); 415 ValueSetExpansionOutcome res; 416 if (cacheOk) { 417 res = txCache.getExpansion(cacheToken); 418 if (res != null) 419 return res; 420 } 421 p.setParameter("includeDefinition", false); 422 p.setParameter("excludeNested", !heirarchical); 423 424 // ok, first we try to expand locally 425 try { 426 ValueSetExpanderSimple vse = new ValueSetExpanderSimple(this); 427 res = vse.doExpand(vs, p); 428 if (!res.getValueset().hasUrl()) 429 throw new Error("no url in expand value set"); 430 txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT); 431 return res; 432 } catch (Exception e) { 433 } 434 435 // if that failed, we try to expand on the server 436 if (noTerminologyServer) 437 return new ValueSetExpansionOutcome("Error expanding ValueSet: running without terminology services", TerminologyServiceErrorClass.NOSERVICE); 438 Map<String, String> params = new HashMap<String, String>(); 439 params.put("_limit", Integer.toString(expandCodesLimit )); 440 params.put("_incomplete", "true"); 441 tlog("$expand on "+txCache.summary(vs)); 442 try { 443 ValueSet result = txServer.expandValueset(vs, p, params); 444 if (!result.hasUrl()) 445 result.setUrl(vs.getUrl()); 446 if (!result.hasUrl()) 447 throw new Error("no url in expand value set 2"); 448 res = new ValueSetExpansionOutcome(result); 449 } catch (Exception e) { 450 res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); 451 } 452 txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); 453 return res; 454 } 455 456 457 private boolean hasTooCostlyExpansion(ValueSet valueset) { 458 return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly"); 459 } 460 // --- validate code ------------------------------------------------------------------------------- 461 462 @Override 463 public ValidationResult validateCode(String system, String code, String display) { 464 Coding c = new Coding(system, code, display); 465 return validateCode(c, null); 466 } 467 468 @Override 469 public ValidationResult validateCode(String system, String code, String display, ValueSet vs) { 470 Coding c = new Coding(system, code, display); 471 return validateCode(c, vs); 472 } 473 474 @Override 475 public ValidationResult validateCode(String code, ValueSet vs) { 476 Coding c = new Coding(null, code, null); 477 return doValidateCode(c, vs, true); 478 } 479 480 @Override 481 public ValidationResult validateCode(String system, String code, String display, ConceptSetComponent vsi) { 482 Coding c = new Coding(system, code, display); 483 ValueSet vs = new ValueSet(); 484 vs.setUrl(Utilities.makeUuidUrn()); 485 vs.getCompose().addInclude(vsi); 486 return validateCode(c, vs); 487 } 488 489 @Override 490 public ValidationResult validateCode(Coding code, ValueSet vs) { 491 return doValidateCode(code, vs, false); 492 } 493 494 public ValidationResult doValidateCode(Coding code, ValueSet vs, boolean implySystem) { 495 CacheToken cacheToken = txCache.generateValidationToken(code, vs); 496 ValidationResult res = txCache.getValidation(cacheToken); 497 if (res != null) 498 return res; 499 500 // ok, first we try to validate locally 501 try { 502 ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(vs, this); 503 res = vsc.validateCode(code); 504 txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); 505 return res; 506 } catch (Exception e) { 507 } 508 509 // if that failed, we try to validate on the server 510 if (noTerminologyServer) 511 return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE); 512 String csumm = txCache.summary(code); 513 tlog("$validate "+csumm+" for "+ txCache.summary(vs)); 514 try { 515 Parameters pIn = new Parameters(); 516 pIn.addParameter().setName("coding").setValue(code); 517 if (implySystem) 518 pIn.addParameter().setName("implySystem").setValue(new BooleanType(true)); 519 res = validateOnServer(vs, pIn); 520 } catch (Exception e) { 521 res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()); 522 } 523 txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); 524 return res; 525 } 526 527 @Override 528 public ValidationResult validateCode(CodeableConcept code, ValueSet vs) { 529 CacheToken cacheToken = txCache.generateValidationToken(code, vs); 530 ValidationResult res = txCache.getValidation(cacheToken); 531 if (res != null) 532 return res; 533 534 // ok, first we try to validate locally 535 try { 536 ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(vs, this); 537 res = vsc.validateCode(code); 538 txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT); 539 return res; 540 } catch (Exception e) { 541 } 542 543 // if that failed, we try to validate on the server 544 if (noTerminologyServer) 545 return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE); 546 tlog("$validate "+txCache.summary(code)+" for "+ txCache.summary(vs)); 547 try { 548 Parameters pIn = new Parameters(); 549 pIn.addParameter().setName("codeableConcept").setValue(code); 550 res = validateOnServer(vs, pIn); 551 } catch (Exception e) { 552 res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()); 553 } 554 txCache.cacheValidation(cacheToken, res, TerminologyCache.PERMANENT); 555 return res; 556 } 557 558 private ValidationResult validateOnServer(ValueSet vs, Parameters pin) { 559 if (vs != null) 560 pin.addParameter().setName("valueSet").setResource(vs); 561 for (ParametersParameterComponent pp : pin.getParameter()) 562 if (pp.getName().equals("profile")) 563 throw new Error("Can only specify profile in the context"); 564 if (expParameters == null) 565 throw new Error("No ExpansionProfile provided"); 566 pin.addParameter().setName("profile").setResource(expParameters); 567 Parameters pOut; 568 if (vs == null) 569 pOut = txServer.operateType(CodeSystem.class, "validate-code", pin); 570 else 571 pOut = txServer.operateType(ValueSet.class, "validate-code", pin); 572 boolean ok = false; 573 String message = "No Message returned"; 574 String display = null; 575 TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN; 576 for (ParametersParameterComponent p : pOut.getParameter()) { 577 if (p.getName().equals("result")) 578 ok = ((BooleanType) p.getValue()).getValue().booleanValue(); 579 else if (p.getName().equals("message")) 580 message = ((StringType) p.getValue()).getValue(); 581 else if (p.getName().equals("display")) 582 display = ((StringType) p.getValue()).getValue(); 583 else if (p.getName().equals("cause")) { 584 try { 585 IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue()); 586 if (it == IssueType.UNKNOWN) 587 err = TerminologyServiceErrorClass.UNKNOWN; 588 else if (it == IssueType.NOTSUPPORTED) 589 err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED; 590 } catch (FHIRException e) { 591 } 592 } 593 } 594 if (!ok) 595 return new ValidationResult(IssueSeverity.ERROR, message, err); 596 else if (message != null && !message.equals("No Message returned")) 597 return new ValidationResult(IssueSeverity.WARNING, message, new ConceptDefinitionComponent().setDisplay(display)); 598 else if (display != null) 599 return new ValidationResult(new ConceptDefinitionComponent().setDisplay(display)); 600 else 601 return new ValidationResult(new ConceptDefinitionComponent()); 602 } 603 604 // -------------------------------------------------------------------------------------------------------------------------------------------------------- 605 606 public void initTS(String cachePath) throws Exception { 607 txCache = new TerminologyCache(lock, cachePath); 608 } 609 610 @Override 611 public List<ConceptMap> findMapsForSource(String url) throws FHIRException { 612 synchronized (lock) { 613 List<ConceptMap> res = new ArrayList<ConceptMap>(); 614 for (ConceptMap map : maps.values()) 615 if (((Reference) map.getSource()).getReference().equals(url)) 616 res.add(map); 617 return res; 618 } 619 } 620 621 public boolean isCanRunWithoutTerminology() { 622 return canRunWithoutTerminology; 623 } 624 625 public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) { 626 this.canRunWithoutTerminology = canRunWithoutTerminology; 627 } 628 629 public void setLogger(ILoggingService logger) { 630 this.logger = logger; 631 } 632 633 public Parameters getExpansionParameters() { 634 return expParameters; 635 } 636 637 public void setExpansionProfile(Parameters expParameters) { 638 this.expParameters = expParameters; 639 } 640 641 @Override 642 public boolean isNoTerminologyServer() { 643 return noTerminologyServer; 644 } 645 646 public String getName() { 647 return name; 648 } 649 650 public void setName(String name) { 651 this.name = name; 652 } 653 654 @Override 655 public Set<String> getResourceNamesAsSet() { 656 Set<String> res = new HashSet<String>(); 657 res.addAll(getResourceNames()); 658 return res; 659 } 660 661 public boolean isAllowLoadingDuplicates() { 662 return allowLoadingDuplicates; 663 } 664 665 public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) { 666 this.allowLoadingDuplicates = allowLoadingDuplicates; 667 } 668 669 @SuppressWarnings("unchecked") 670 @Override 671 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException { 672 if (class_ == StructureDefinition.class) 673 uri = ProfileUtilities.sdNs(uri, getOverrideVersionNs()); 674 synchronized (lock) { 675 676 if (uri.startsWith("http:") || uri.startsWith("https:")) { 677 String version = null; 678 if (uri.contains("#")) 679 uri = uri.substring(0, uri.indexOf("#")); 680 if (class_ == Resource.class || class_ == null) { 681 if (structures.containsKey(uri)) 682 return (T) structures.get(uri); 683 if (guides.containsKey(uri)) 684 return (T) guides.get(uri); 685 if (valueSets.containsKey(uri)) 686 return (T) valueSets.get(uri); 687 if (codeSystems.containsKey(uri)) 688 return (T) codeSystems.get(uri); 689 if (operations.containsKey(uri)) 690 return (T) operations.get(uri); 691 if (searchParameters.containsKey(uri)) 692 return (T) searchParameters.get(uri); 693 if (plans.containsKey(uri)) 694 return (T) plans.get(uri); 695 if (maps.containsKey(uri)) 696 return (T) maps.get(uri); 697 if (transforms.containsKey(uri)) 698 return (T) transforms.get(uri); 699 if (questionnaires.containsKey(uri)) 700 return (T) questionnaires.get(uri); 701 return null; 702 } else if (class_ == ImplementationGuide.class) { 703 return (T) guides.get(uri); 704 } else if (class_ == StructureDefinition.class) { 705 return (T) structures.get(uri); 706 } else if (class_ == ValueSet.class) { 707 return (T) valueSets.get(uri); 708 } else if (class_ == CodeSystem.class) { 709 return (T) codeSystems.get(uri); 710 } else if (class_ == ConceptMap.class) { 711 return (T) maps.get(uri); 712 } else if (class_ == PlanDefinition.class) { 713 return (T) plans.get(uri); 714 } else if (class_ == OperationDefinition.class) { 715 OperationDefinition od = operations.get(uri); 716 return (T) od; 717 } else if (class_ == SearchParameter.class) { 718 SearchParameter res = searchParameters.get(uri); 719 if (res == null) { 720 StringBuilder b = new StringBuilder(); 721 for (String s : searchParameters.keySet()) { 722 b.append(s); 723 b.append("\r\n"); 724 } 725 } 726 if (res != null) 727 return (T) res; 728 } 729 } 730 if (class_ == Questionnaire.class) 731 return (T) questionnaires.get(uri); 732 if (class_ == null) { 733 if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) 734 return null; 735 736 // it might be a special URL. 737 if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) { 738 Resource res = null; // findTxValueSet(uri); 739 if (res != null) 740 return (T) res; 741 } 742 return null; 743 } 744 throw new FHIRException("not done yet: can't fetch "+uri); 745 } 746 } 747 748 private Set<String> notCanonical = new HashSet<String>(); 749 750 private String overrideVersionNs; 751 752// private MetadataResource findTxValueSet(String uri) { 753// MetadataResource res = expansionCache.getStoredResource(uri); 754// if (res != null) 755// return res; 756// synchronized (lock) { 757// if (notCanonical.contains(uri)) 758// return null; 759// } 760// try { 761// tlog("get canonical "+uri); 762// res = txServer.getCanonical(ValueSet.class, uri); 763// } catch (Exception e) { 764// synchronized (lock) { 765// notCanonical.add(uri); 766// } 767// return null; 768// } 769// if (res != null) 770// try { 771// expansionCache.storeResource(res); 772// } catch (IOException e) { 773// } 774// return res; 775// } 776 777 @Override 778 public Resource fetchResourceById(String type, String uri) { 779 synchronized (lock) { 780 String[] parts = uri.split("\\/"); 781 if (!Utilities.noString(type) && parts.length == 1) 782 return allResourcesById.get(type).get(parts[0]); 783 if (parts.length >= 2) { 784 if (!Utilities.noString(type)) 785 if (!type.equals(parts[parts.length-2])) 786 throw new Error("Resource type mismatch for "+type+" / "+uri); 787 return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]); 788 } else 789 throw new Error("Unable to process request for resource for "+type+" / "+uri); 790 } 791 } 792 793 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 794 try { 795 return fetchResourceWithException(class_, uri); 796 } catch (FHIRException e) { 797 throw new Error(e); 798 } 799 } 800 801 @Override 802 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 803 try { 804 return fetchResourceWithException(class_, uri) != null; 805 } catch (Exception e) { 806 return false; 807 } 808 } 809 810 811 public TranslationServices translator() { 812 return translator; 813 } 814 815 public void setTranslator(TranslationServices translator) { 816 this.translator = translator; 817 } 818 819 public class NullTranslator implements TranslationServices { 820 821 @Override 822 public String translate(String context, String value, String targetLang) { 823 return value; 824 } 825 826 @Override 827 public String translate(String context, String value) { 828 return value; 829 } 830 831 @Override 832 public String toStr(float value) { 833 return null; 834 } 835 836 @Override 837 public String toStr(Date value) { 838 return null; 839 } 840 841 @Override 842 public String translateAndFormat(String contest, String lang, String value, Object... args) { 843 return String.format(value, args); 844 } 845 846 @Override 847 public Map<String, String> translations(String value) { 848 // TODO Auto-generated method stub 849 return null; 850 } 851 852 @Override 853 public Set<String> listTranslations(String category) { 854 // TODO Auto-generated method stub 855 return null; 856 } 857 858 } 859 860 public void reportStatus(JsonObject json) { 861 synchronized (lock) { 862 json.addProperty("codeystem-count", codeSystems.size()); 863 json.addProperty("valueset-count", valueSets.size()); 864 json.addProperty("conceptmap-count", maps.size()); 865 json.addProperty("transforms-count", transforms.size()); 866 json.addProperty("structures-count", structures.size()); 867 json.addProperty("guides-count", guides.size()); 868 } 869 } 870 871 872 public void dropResource(Resource r) throws FHIRException { 873 dropResource(r.fhirType(), r.getId()); 874 } 875 876 public void dropResource(String fhirType, String id) { 877 synchronized (lock) { 878 879 Map<String, Resource> map = allResourcesById.get(fhirType); 880 if (map == null) { 881 map = new HashMap<String, Resource>(); 882 allResourcesById.put(fhirType, map); 883 } 884 if (map.containsKey(id)) 885 map.remove(id); 886 887 if (fhirType.equals("StructureDefinition")) 888 dropMetadataResource(structures, id); 889 else if (fhirType.equals("ImplementationGuide")) 890 dropMetadataResource(guides, id); 891 else if (fhirType.equals("ValueSet")) 892 dropMetadataResource(valueSets, id); 893 else if (fhirType.equals("CodeSystem")) 894 dropMetadataResource(codeSystems, id); 895 else if (fhirType.equals("OperationDefinition")) 896 dropMetadataResource(operations, id); 897 else if (fhirType.equals("Questionnaire")) 898 dropMetadataResource(questionnaires, id); 899 else if (fhirType.equals("ConceptMap")) 900 dropMetadataResource(maps, id); 901 else if (fhirType.equals("StructureMap")) 902 dropMetadataResource(transforms, id); 903 else if (fhirType.equals("NamingSystem")) 904 for (int i = systems.size()-1; i >= 0; i--) { 905 if (systems.get(i).getId().equals(id)) 906 systems.remove(i); 907 } 908 } 909 } 910 911 private <T extends MetadataResource> void dropMetadataResource(Map<String, T> map, String id) { 912 T res = map.get(id); 913 if (res != null) { 914 map.remove(id); 915 if (map.containsKey(res.getUrl())) 916 map.remove(res.getUrl()); 917 if (res.getVersion() != null) 918 if (map.containsKey(res.getUrl()+"|"+res.getVersion())) 919 map.remove(res.getUrl()+"|"+res.getVersion()); 920 } 921 } 922 923 @Override 924 public List<MetadataResource> allConformanceResources() { 925 synchronized (lock) { 926 List<MetadataResource> result = new ArrayList<MetadataResource>(); 927 result.addAll(structures.values()); 928 result.addAll(guides.values()); 929 result.addAll(codeSystems.values()); 930 result.addAll(valueSets.values()); 931 result.addAll(maps.values()); 932 result.addAll(transforms.values()); 933 result.addAll(plans.values()); 934 return result; 935 } 936 } 937 938 public void addNonSupportedCodeSystems(String s) { 939 synchronized (lock) { 940 nonSupportedCodeSystems.add(s); 941 } 942 } 943 944 public String listNonSupportedSystems() { 945 synchronized (lock) { 946 String sl = null; 947 for (String s : nonSupportedCodeSystems) 948 sl = sl == null ? s : sl + "\r\n" + s; 949 return sl; 950 } 951 } 952 953 954 public int totalCount() { 955 synchronized (lock) { 956 return valueSets.size() + maps.size() + structures.size() + transforms.size(); 957 } 958 } 959 960 public List<ConceptMap> listMaps() { 961 List<ConceptMap> m = new ArrayList<ConceptMap>(); 962 synchronized (lock) { 963 m.addAll(maps.values()); 964 } 965 return m; 966 } 967 968 public List<StructureMap> listTransforms() { 969 List<StructureMap> m = new ArrayList<StructureMap>(); 970 synchronized (lock) { 971 m.addAll(transforms.values()); 972 } 973 return m; 974 } 975 976 public StructureMap getTransform(String code) { 977 synchronized (lock) { 978 return transforms.get(code); 979 } 980 } 981 982 public List<StructureDefinition> listStructures() { 983 List<StructureDefinition> m = new ArrayList<StructureDefinition>(); 984 synchronized (lock) { 985 m.addAll(structures.values()); 986 } 987 return m; 988 } 989 990 public StructureDefinition getStructure(String code) { 991 synchronized (lock) { 992 return structures.get(code); 993 } 994 } 995 996 @Override 997 public String oid2Uri(String oid) { 998 synchronized (lock) { 999 String uri = OIDUtils.getUriForOid(oid); 1000 if (uri != null) 1001 return uri; 1002 for (NamingSystem ns : systems) { 1003 if (hasOid(ns, oid)) { 1004 uri = getUri(ns); 1005 if (uri != null) 1006 return null; 1007 } 1008 } 1009 } 1010 return null; 1011 } 1012 1013 1014 private String getUri(NamingSystem ns) { 1015 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 1016 if (id.getType() == NamingSystemIdentifierType.URI) 1017 return id.getValue(); 1018 } 1019 return null; 1020 } 1021 1022 private boolean hasOid(NamingSystem ns, String oid) { 1023 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 1024 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) 1025 return true; 1026 } 1027 return false; 1028 } 1029 1030 public void cacheVS(JsonObject json, Map<String, ValidationResult> t) { 1031 synchronized (lock) { 1032 validationCache.put(json.get("url").getAsString(), t); 1033 } 1034 } 1035 1036 public SearchParameter getSearchParameter(String code) { 1037 synchronized (lock) { 1038 return searchParameters.get(code); 1039 } 1040 } 1041 1042 @Override 1043 public String getOverrideVersionNs() { 1044 return overrideVersionNs; 1045 } 1046 1047 @Override 1048 public void setOverrideVersionNs(String value) { 1049 overrideVersionNs = value; 1050 } 1051 1052 @Override 1053 public ILoggingService getLogger() { 1054 return logger; 1055 } 1056 1057 @Override 1058 public StructureDefinition fetchTypeDefinition(String typeName) { 1059 if (Utilities.isAbsoluteUrl(typeName)) 1060 return fetchResource(StructureDefinition.class, typeName); 1061 else 1062 return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+typeName); 1063 } 1064 1065 1066}