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