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.formats.JsonParser; 019import org.hl7.fhir.r4.model.BooleanType; 020import org.hl7.fhir.r4.model.Bundle; 021import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; 022import org.hl7.fhir.r4.model.CodeSystem; 023import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; 024import org.hl7.fhir.r4.model.CodeSystem.CodeSystemHierarchyMeaning; 025import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 026import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionDesignationComponent; 027import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 028import org.hl7.fhir.r4.model.NamingSystem.NamingSystemIdentifierType; 029import org.hl7.fhir.r4.model.NamingSystem.NamingSystemUniqueIdComponent; 030import org.hl7.fhir.r4.model.CodeableConcept; 031import org.hl7.fhir.r4.model.Coding; 032import org.hl7.fhir.r4.model.ConceptMap; 033import org.hl7.fhir.r4.model.Constants; 034import org.hl7.fhir.r4.model.ExpansionProfile; 035import org.hl7.fhir.r4.model.MetadataResource; 036import org.hl7.fhir.r4.model.NamingSystem; 037import org.hl7.fhir.r4.model.OperationDefinition; 038import org.hl7.fhir.r4.model.OperationOutcome; 039import org.hl7.fhir.r4.model.Parameters; 040import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; 041import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 042import org.hl7.fhir.r4.model.PrimitiveType; 043import org.hl7.fhir.r4.model.Questionnaire; 044import org.hl7.fhir.r4.model.Reference; 045import org.hl7.fhir.r4.model.Resource; 046import org.hl7.fhir.r4.model.SearchParameter; 047import org.hl7.fhir.r4.model.StringType; 048import org.hl7.fhir.r4.model.StructureDefinition; 049import org.hl7.fhir.r4.model.StructureMap; 050import org.hl7.fhir.r4.model.UriType; 051import org.hl7.fhir.r4.model.ValueSet; 052import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; 053import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; 054import org.hl7.fhir.r4.model.ValueSet.ValueSetComposeComponent; 055import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; 056import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 057import org.hl7.fhir.r4.terminologies.ValueSetExpander.ETooCostly; 058import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorClass; 059import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 060import org.hl7.fhir.r4.terminologies.ValueSetExpanderFactory; 061import org.hl7.fhir.r4.terminologies.ValueSetExpansionCache; 062import org.hl7.fhir.r4.utils.ToolingExtensions; 063import org.hl7.fhir.r4.utils.client.FHIRToolingClient; 064import org.hl7.fhir.exceptions.DefinitionException; 065import org.hl7.fhir.exceptions.FHIRException; 066import org.hl7.fhir.exceptions.NoTerminologyServiceException; 067import org.hl7.fhir.exceptions.TerminologyServiceException; 068import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 069import org.hl7.fhir.utilities.OIDUtils; 070import org.hl7.fhir.utilities.TextFile; 071import org.hl7.fhir.utilities.TranslationServices; 072import org.hl7.fhir.utilities.Utilities; 073import org.hl7.fhir.utilities.validation.ValidationMessage; 074import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 075import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 076import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 077 078import com.google.gson.JsonObject; 079import com.google.gson.JsonSyntaxException; 080 081import ca.uhn.fhir.rest.annotation.Metadata; 082 083public abstract class BaseWorkerContext implements IWorkerContext { 084 085 private Object lock = new Object(); // used as a lock for the data that follows 086 087 private Map<String, Map<String, Resource>> allResourcesById = new HashMap<String, Map<String, Resource>>(); 088 // all maps are to the full URI 089 private Map<String, CodeSystem> codeSystems = new HashMap<String, CodeSystem>(); 090 private Set<String> nonSupportedCodeSystems = new HashSet<String>(); 091 private Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>(); 092 private Map<String, ConceptMap> maps = new HashMap<String, ConceptMap>(); 093 private Map<String, StructureMap> transforms = new HashMap<String, StructureMap>(); 094// private Map<String, StructureDefinition> profiles = new HashMap<String, StructureDefinition>(); 095 private Map<String, StructureDefinition> structures = new HashMap<String, StructureDefinition>(); 096// private Map<String, StructureDefinition> extensionDefinitions = new HashMap<String, StructureDefinition>(); 097 private Map<String, SearchParameter> searchParameters = new HashMap<String, SearchParameter>(); 098 private Map<String, Questionnaire> questionnaires = new HashMap<String, Questionnaire>(); 099 private Map<String, OperationDefinition> operations = new HashMap<String, OperationDefinition>(); 100 private List<NamingSystem> systems = new ArrayList<NamingSystem>(); 101 102 103 private ValueSetExpansionCache expansionCache = new ValueSetExpansionCache(this, lock); 104 protected boolean cacheValidation; // if true, do an expansion and cache the expansion 105 private Set<String> failed = new HashSet<String>(); // value sets for which we don't try to do expansion, since the first attempt to get a comprehensive expansion was not successful 106 protected Map<String, Map<String, ValidationResult>> validationCache = new HashMap<String, Map<String,ValidationResult>>(); 107 protected String tsServer; 108 protected String validationCachePath; 109 protected String name; 110 private boolean allowLoadingDuplicates; 111 112 // private ValueSetExpansionCache expansionCache; // 113 114 protected FHIRToolingClient txServer; 115 private Bundle bndCodeSystems; 116 private boolean canRunWithoutTerminology; 117 protected boolean noTerminologyServer; 118 protected String cache; 119 private int expandCodesLimit = 1000; 120 protected ILoggingService logger; 121 protected ExpansionProfile expProfile; 122 private TranslationServices translator = new NullTranslator(); 123 124 public BaseWorkerContext() { 125 super(); 126 } 127 128 public BaseWorkerContext(Map<String, CodeSystem> codeSystems, Map<String, ValueSet> valueSets, Map<String, ConceptMap> maps, Map<String, StructureDefinition> profiles) { 129 super(); 130 this.codeSystems = codeSystems; 131 this.valueSets = valueSets; 132 this.maps = maps; 133 this.structures = profiles; 134 } 135 136 protected void copy(BaseWorkerContext other) { 137 synchronized (other.lock) { // tricky, because you need to lock this as well, but it's really not in use yet 138 allResourcesById.putAll(other.allResourcesById); 139 translator = other.translator; 140 codeSystems.putAll(other.codeSystems); 141 nonSupportedCodeSystems.addAll(other.nonSupportedCodeSystems); 142 valueSets.putAll(other.valueSets); 143 maps.putAll(other.maps); 144 transforms.putAll(other.transforms); 145 structures.putAll(other.structures); 146 searchParameters.putAll(other.searchParameters); 147 questionnaires.putAll(other.questionnaires); 148 operations.putAll(other.operations); 149 systems.addAll(other.systems); 150 151 allowLoadingDuplicates = other.allowLoadingDuplicates; 152 cacheValidation = other.cacheValidation; 153 tsServer = other.tsServer; 154 validationCachePath = other.validationCachePath; 155 name = other.name; 156 txServer = other.txServer; 157 bndCodeSystems = other.bndCodeSystems; 158 canRunWithoutTerminology = other.canRunWithoutTerminology; 159 noTerminologyServer = other.noTerminologyServer; 160 cache = other.cache; 161 expandCodesLimit = other.expandCodesLimit; 162 logger = other.logger; 163 expProfile = other.expProfile; 164 } 165 } 166 167 public void cacheResource(Resource r) throws FHIRException { 168 synchronized (lock) { 169 Map<String, Resource> map = allResourcesById.get(r.fhirType()); 170 if (map == null) { 171 map = new HashMap<String, Resource>(); 172 allResourcesById.put(r.fhirType(), map); 173 } 174 map.put(r.getId(), r); 175 176 if (r instanceof MetadataResource) { 177 MetadataResource m = (MetadataResource) r; 178 String url = m.getUrl(); 179 if (!allowLoadingDuplicates && hasResource(r.getClass(), url)) 180 throw new DefinitionException("Duplicate Resource " + url); 181 if (r instanceof StructureDefinition) 182 seeMetadataResource((StructureDefinition) m, structures, false); 183 else if (r instanceof ValueSet) 184 seeMetadataResource((ValueSet) m, valueSets, false); 185 else if (r instanceof CodeSystem) 186 seeMetadataResource((CodeSystem) m, codeSystems, false); 187 else if (r instanceof SearchParameter) 188 seeMetadataResource((SearchParameter) m, searchParameters, false); 189 else if (r instanceof OperationDefinition) 190 seeMetadataResource((OperationDefinition) m, operations, false); 191 else if (r instanceof Questionnaire) 192 seeMetadataResource((Questionnaire) m, questionnaires, true); 193 else if (r instanceof ConceptMap) 194 seeMetadataResource((ConceptMap) m, maps, false); 195 else if (r instanceof StructureMap) 196 seeMetadataResource((StructureMap) m, transforms, false); 197 else if (r instanceof NamingSystem) 198 systems.add((NamingSystem) r); 199 } 200 } 201 } 202 203 /* 204 * Compare business versions, returning "true" if the candidate newer version is in fact newer than the oldVersion 205 * Comparison will work for strictly numeric versions as well as multi-level versions separated by ., -, _, : or space 206 * Failing that, it will do unicode-based character ordering. 207 * E.g. 1.5.3 < 1.14.3 208 * 2017-3-10 < 2017-12-7 209 * A3 < T2 210 */ 211 private boolean laterVersion(String newVersion, String oldVersion) { 212 // Compare business versions, retur 213 newVersion = newVersion.trim(); 214 oldVersion = oldVersion.trim(); 215 if (StringUtils.isNumeric(newVersion) && StringUtils.isNumeric(oldVersion)) 216 return Double.parseDouble(newVersion) > Double.parseDouble(oldVersion); 217 else if (hasDelimiter(newVersion, oldVersion, ".")) 218 return laterDelimitedVersion(newVersion, oldVersion, "\\."); 219 else if (hasDelimiter(newVersion, oldVersion, "-")) 220 return laterDelimitedVersion(newVersion, oldVersion, "\\-"); 221 else if (hasDelimiter(newVersion, oldVersion, "_")) 222 return laterDelimitedVersion(newVersion, 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 { 228 return newVersion.compareTo(oldVersion) > 0; 229 } 230 } 231 232 /* 233 * Returns true if both strings include the delimiter and have the same number of occurrences of it 234 */ 235 private boolean hasDelimiter(String s1, String s2, String delimiter) { 236 return s1.contains(delimiter) && s2.contains(delimiter) && s1.split(delimiter).length == s2.split(delimiter).length; 237 } 238 239 private boolean laterDelimitedVersion(String newVersion, String oldVersion, String delimiter) { 240 String[] newParts = newVersion.split(delimiter); 241 String[] oldParts = oldVersion.split(delimiter); 242 for (int i = 0; i < newParts.length; i++) { 243 if (!newParts[i].equals(oldParts[i])) 244 return laterVersion(newParts[i], oldParts[i]); 245 } 246 // This should never happen 247 throw new Error("Delimited versions have exact match for delimiter '"+delimiter+"' : "+ Arrays.asList(newParts)+" vs "+Arrays.asList(oldParts)); 248 } 249 250 protected <T extends MetadataResource> void seeMetadataResource(T r, Map<String, T> map, boolean addId) throws FHIRException { 251 if (addId) 252 map.put(r.getId(), r); // todo: why? 253 if (!map.containsKey(r.getUrl())) 254 map.put(r.getUrl(), r); 255 else { 256 // 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 257 MetadataResource existingResource = (MetadataResource)map.get(r.getUrl()); 258 if (r.hasVersion() && existingResource.hasVersion() && !r.getVersion().equals(existingResource.getVersion())) { 259 if (laterVersion(r.getVersion(), existingResource.getVersion())) { 260 map.remove(r.getUrl()); 261 map.put(r.getUrl(), r); 262 } 263 } else 264 map.remove(r.getUrl()); 265 map.put(r.getUrl(), r); 266// throw new FHIRException("Multiple declarations of resource with same canonical URL (" + r.getUrl() + ") and version (" + (r.hasVersion() ? r.getVersion() : "" ) + ")"); 267 } 268 if (r.hasVersion()) 269 map.put(r.getUrl()+"|"+r.getVersion(), r); 270 } 271 272 @Override 273 public CodeSystem fetchCodeSystem(String system) { 274 synchronized (lock) { 275 return codeSystems.get(system); 276 } 277 } 278 279 @Override 280 public boolean supportsSystem(String system) throws TerminologyServiceException { 281 synchronized (lock) { 282 if (codeSystems.containsKey(system)) 283 return true; 284 else if (nonSupportedCodeSystems.contains(system)) 285 return false; 286 else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) 287 return false; 288 else { 289 if (noTerminologyServer) 290 return false; 291 if (bndCodeSystems == null) { 292 try { 293 tlog("Terminology server: Check for supported code systems for "+system); 294 bndCodeSystems = txServer.fetchFeed(txServer.getAddress()+"/CodeSystem?content-mode=not-present&_summary=true&_count=1000"); 295 } catch (Exception e) { 296 if (canRunWithoutTerminology) { 297 noTerminologyServer = true; 298 log("==============!! Running without terminology server !! =============="); 299 log("txServer = "+txServer.getAddress()); 300 log("Error = "+e.getMessage()+""); 301 log("====================================================================="); 302 return false; 303 } else 304 throw new TerminologyServiceException(e); 305 } 306 } 307 if (bndCodeSystems != null) { 308 for (BundleEntryComponent be : bndCodeSystems.getEntry()) { 309 CodeSystem cs = (CodeSystem) be.getResource(); 310 if (!codeSystems.containsKey(cs.getUrl())) { 311 codeSystems.put(cs.getUrl(), null); 312 } 313 } 314 } 315 if (codeSystems.containsKey(system)) 316 return true; 317 } 318 nonSupportedCodeSystems.add(system); 319 return false; 320 } 321 } 322 323 private void log(String message) { 324 if (logger != null) 325 logger.logMessage(message); 326 else 327 System.out.println(message); 328 } 329 330 @Override 331 public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) { 332 try { 333 if (vs.hasExpansion()) { 334 return new ValueSetExpansionOutcome(vs.copy()); 335 } 336 String cacheFn = null; 337 if (cache != null) { 338 cacheFn = Utilities.path(cache, determineCacheId(vs, heirarchical)+".json"); 339 if (new File(cacheFn).exists()) 340 return loadFromCache(vs.copy(), cacheFn); 341 } 342 if (cacheOk && vs.hasUrl()) { 343 if (expProfile == null) 344 throw new Exception("No ExpansionProfile provided"); 345 ValueSetExpansionOutcome vse = expansionCache.getExpander().expand(vs, expProfile.setExcludeNested(!heirarchical)); 346 if (vse.getValueset() != null) { 347 if (cache != null) { 348 FileOutputStream s = new FileOutputStream(cacheFn); 349 newJsonParser().compose(new FileOutputStream(cacheFn), vse.getValueset()); 350 s.close(); 351 } 352 } 353 return vse; 354 } else { 355 ValueSetExpansionOutcome res = expandOnServer(vs, cacheFn); 356 if (cacheFn != null) { 357 if (res.getValueset() != null) { 358 saveToCache(res.getValueset(), cacheFn); 359 } else { 360 OperationOutcome oo = new OperationOutcome(); 361 oo.addIssue().getDetails().setText(res.getError()); 362 saveToCache(oo, cacheFn); 363 } 364 } 365 return res; 366 } 367 } catch (NoTerminologyServiceException e) { 368 return new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.NOSERVICE); 369 } catch (Exception e) { 370 return new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); 371 } 372 } 373 374 private ValueSetExpansionOutcome loadFromCache(ValueSet vs, String cacheFn) throws FileNotFoundException, Exception { 375 JsonParser parser = new JsonParser(); 376 Resource r = parser.parse(new FileInputStream(cacheFn)); 377 if (r instanceof OperationOutcome) 378 return new ValueSetExpansionOutcome(((OperationOutcome) r).getIssue().get(0).getDetails().getText(), TerminologyServiceErrorClass.UNKNOWN); 379 else { 380 vs.setExpansion(((ValueSet) r).getExpansion()); // because what is cached might be from a different value set 381 return new ValueSetExpansionOutcome(vs); 382 } 383 } 384 385 private void saveToCache(Resource res, String cacheFn) throws FileNotFoundException, Exception { 386 JsonParser parser = new JsonParser(); 387 parser.compose(new FileOutputStream(cacheFn), res); 388 } 389 390 private String determineCacheId(ValueSet vs, boolean heirarchical) throws Exception { 391 // just the content logical definition is hashed 392 ValueSet vsid = new ValueSet(); 393 vsid.setCompose(vs.getCompose()); 394 JsonParser parser = new JsonParser(); 395 parser.setOutputStyle(OutputStyle.NORMAL); 396 ByteArrayOutputStream b = new ByteArrayOutputStream(); 397 parser.compose(b, vsid); 398 b.close(); 399 String s = new String(b.toByteArray(), Charsets.UTF_8); 400 // any code systems we can find, we add these too. 401 for (ConceptSetComponent inc : vs.getCompose().getInclude()) { 402 CodeSystem cs = fetchCodeSystem(inc.getSystem()); 403 if (cs != null) { 404 String css = cacheValue(cs); 405 s = s + css; 406 } 407 } 408 s = s + "-"+Boolean.toString(heirarchical); 409 String r = Integer.toString(s.hashCode()); 410 // TextFile.stringToFile(s, Utilities.path(cache, r+".id.json")); 411 return r; 412 } 413 414 415 private String cacheValue(CodeSystem cs) throws IOException { 416 CodeSystem csid = new CodeSystem(); 417 csid.setId(cs.getId()); 418 csid.setVersion(cs.getVersion()); 419 csid.setContent(cs.getContent()); 420 csid.setHierarchyMeaning(CodeSystemHierarchyMeaning.GROUPEDBY); 421 for (ConceptDefinitionComponent cc : cs.getConcept()) 422 csid.getConcept().add(processCSConcept(cc)); 423 JsonParser parser = new JsonParser(); 424 parser.setOutputStyle(OutputStyle.NORMAL); 425 ByteArrayOutputStream b = new ByteArrayOutputStream(); 426 parser.compose(b, csid); 427 b.close(); 428 return new String(b.toByteArray(), Charsets.UTF_8); 429 } 430 431 432 private ConceptDefinitionComponent processCSConcept(ConceptDefinitionComponent cc) { 433 ConceptDefinitionComponent ccid = new ConceptDefinitionComponent(); 434 ccid.setCode(cc.getCode()); 435 ccid.setDisplay(cc.getDisplay()); 436 for (ConceptDefinitionComponent cci : cc.getConcept()) 437 ccid.getConcept().add(processCSConcept(cci)); 438 return ccid; 439 } 440 441 public ValueSetExpansionOutcome expandOnServer(ValueSet vs, String fn) throws Exception { 442 if (noTerminologyServer) 443 return new ValueSetExpansionOutcome("Error expanding ValueSet: running without terminology services", TerminologyServiceErrorClass.NOSERVICE); 444 if (expProfile == null) 445 throw new Exception("No ExpansionProfile provided"); 446 447 try { 448 Map<String, String> params = new HashMap<String, String>(); 449 params.put("_limit", Integer.toString(expandCodesLimit )); 450 params.put("_incomplete", "true"); 451 tlog("Terminology Server: $expand on "+getVSSummary(vs)); 452 ValueSet result = txServer.expandValueset(vs, expProfile.setIncludeDefinition(false), params); 453 return new ValueSetExpansionOutcome(result); 454 } catch (Exception e) { 455 return new ValueSetExpansionOutcome("Error expanding ValueSet \""+vs.getUrl()+": "+e.getMessage(), TerminologyServiceErrorClass.UNKNOWN); 456 } 457 } 458 459 private String getVSSummary(ValueSet vs) { 460 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 461 for (ConceptSetComponent cc : vs.getCompose().getInclude()) 462 b.append("Include "+getIncSummary(cc)); 463 for (ConceptSetComponent cc : vs.getCompose().getExclude()) 464 b.append("Exclude "+getIncSummary(cc)); 465 return b.toString(); 466 } 467 468 private String getIncSummary(ConceptSetComponent cc) { 469 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 470 for (UriType vs : cc.getValueSet()) 471 b.append(vs.asStringValue()); 472 String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : ""; 473 String system = cc.getSystem(); 474 if (cc.hasConcept()) 475 return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd; 476 if (cc.hasFilter()) { 477 String s = ""; 478 for (ConceptSetFilterComponent f : cc.getFilter()) { 479 if (!Utilities.noString(s)) 480 s = s + " & "; 481 s = s + f.getProperty()+" "+f.getOp().toCode()+" "+f.getValue(); 482 } 483 return "from "+system+" where "+s+vsd; 484 } 485 return "All codes from "+system+vsd; 486 } 487 488 private ValidationResult handleByCache(ValueSet vs, Coding coding, boolean tryCache) { 489 String cacheId = cacheId(coding); 490 Map<String, ValidationResult> cache = validationCache.get(vs.getUrl()); 491 if (cache == null) { 492 cache = new HashMap<String, IWorkerContext.ValidationResult>(); 493 validationCache.put(vs.getUrl(), cache); 494 } 495 if (cache.containsKey(cacheId)) 496 return cache.get(cacheId); 497 if (!tryCache) 498 return null; 499 if (!cacheValidation) 500 return null; 501 if (failed.contains(vs.getUrl())) 502 return null; 503 ValueSetExpansionOutcome vse = expandVS(vs, true, false); 504 if (vse.getValueset() == null || notcomplete(vse.getValueset())) { 505 failed.add(vs.getUrl()); 506 return null; 507 } 508 509 ValidationResult res = validateCode(coding, vse.getValueset()); 510 cache.put(cacheId, res); 511 return res; 512 } 513 514 private boolean notcomplete(ValueSet vs) { 515 if (!vs.hasExpansion()) 516 return true; 517 if (!vs.getExpansion().getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/valueset-unclosed").isEmpty()) 518 return true; 519 if (!vs.getExpansion().getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/valueset-toocostly").isEmpty()) 520 return true; 521 return false; 522 } 523 524 private ValidationResult handleByCache(ValueSet vs, CodeableConcept concept, boolean tryCache) { 525 String cacheId = cacheId(concept); 526 Map<String, ValidationResult> cache = validationCache.get(vs.getUrl()); 527 if (cache == null) { 528 cache = new HashMap<String, IWorkerContext.ValidationResult>(); 529 validationCache.put(vs.getUrl(), cache); 530 } 531 if (cache.containsKey(cacheId)) 532 return cache.get(cacheId); 533 534 if (validationCache.containsKey(vs.getUrl()) && validationCache.get(vs.getUrl()).containsKey(cacheId)) 535 return validationCache.get(vs.getUrl()).get(cacheId); 536 if (!tryCache) 537 return null; 538 if (!cacheValidation) 539 return null; 540 if (failed.contains(vs.getUrl())) 541 return null; 542 ValueSetExpansionOutcome vse = expandVS(vs, true, false); 543 if (vse.getValueset() == null || notcomplete(vse.getValueset())) { 544 failed.add(vs.getUrl()); 545 return null; 546 } 547 ValidationResult res = validateCode(concept, vse.getValueset()); 548 cache.put(cacheId, res); 549 return res; 550 } 551 552 private String cacheId(Coding coding) { 553 return "|"+coding.getSystem()+"|"+coding.getVersion()+"|"+coding.getCode()+"|"+coding.getDisplay(); 554 } 555 556 private String cacheId(CodeableConcept cc) { 557 StringBuilder b = new StringBuilder(); 558 for (Coding c : cc.getCoding()) { 559 b.append("#"); 560 b.append(cacheId(c)); 561 } 562 return b.toString(); 563 } 564 565 private ValidationResult verifyCodeExternal(ValueSet vs, Coding coding, boolean tryCache) throws Exception { 566 ValidationResult res = vs == null ? null : handleByCache(vs, coding, tryCache); 567 if (res != null) 568 return res; 569 Parameters pin = new Parameters(); 570 pin.addParameter().setName("coding").setValue(coding); 571 if (vs != null) 572 pin.addParameter().setName("valueSet").setResource(vs); 573 res = serverValidateCode(pin, vs == null); 574 if (vs != null) { 575 Map<String, ValidationResult> cache = validationCache.get(vs.getUrl()); 576 cache.put(cacheId(coding), res); 577 } 578 return res; 579 } 580 581 private ValidationResult verifyCodeExternal(ValueSet vs, CodeableConcept cc, boolean tryCache) throws Exception { 582 ValidationResult res = handleByCache(vs, cc, tryCache); 583 if (res != null) 584 return res; 585 Parameters pin = new Parameters(); 586 pin.addParameter().setName("codeableConcept").setValue(cc); 587 pin.addParameter().setName("valueSet").setResource(vs); 588 res = serverValidateCode(pin, tryCache); 589 Map<String, ValidationResult> cache = validationCache.get(vs.getUrl()); 590 cache.put(cacheId(cc), res); 591 return res; 592 } 593 594 private ValidationResult serverValidateCode(Parameters pin, boolean doCache) throws Exception { 595 if (noTerminologyServer) 596 return new ValidationResult(null, null, TerminologyServiceErrorClass.NOSERVICE); 597 String cacheName = doCache ? generateCacheName(pin) : null; 598 ValidationResult res = loadFromCache(cacheName); 599 if (res != null) 600 return res; 601 tlog("Terminology Server: $validate-code "+describeValidationParameters(pin)); 602 for (ParametersParameterComponent pp : pin.getParameter()) 603 if (pp.getName().equals("profile")) 604 throw new Error("Can only specify profile in the context"); 605 if (expProfile == null) 606 throw new Exception("No ExpansionProfile provided"); 607 pin.addParameter().setName("profile").setResource(expProfile); 608 609 Parameters pout = txServer.operateType(ValueSet.class, "validate-code", pin); 610 boolean ok = false; 611 String message = "No Message returned"; 612 String display = null; 613 TerminologyServiceErrorClass err = TerminologyServiceErrorClass.UNKNOWN; 614 for (ParametersParameterComponent p : pout.getParameter()) { 615 if (p.getName().equals("result")) 616 ok = ((BooleanType) p.getValue()).getValue().booleanValue(); 617 else if (p.getName().equals("message")) 618 message = ((StringType) p.getValue()).getValue(); 619 else if (p.getName().equals("display")) 620 display = ((StringType) p.getValue()).getValue(); 621 else if (p.getName().equals("cause")) { 622 try { 623 IssueType it = IssueType.fromCode(((StringType) p.getValue()).getValue()); 624 if (it == IssueType.UNKNOWN) 625 err = TerminologyServiceErrorClass.UNKNOWN; 626 else if (it == IssueType.NOTSUPPORTED) 627 err = TerminologyServiceErrorClass.VALUESET_UNSUPPORTED; 628 } catch (FHIRException e) { 629 } 630 } 631 } 632 if (!ok) 633 res = new ValidationResult(IssueSeverity.ERROR, message, err); 634 else if (message != null) 635 res = new ValidationResult(IssueSeverity.WARNING, message, new ConceptDefinitionComponent().setDisplay(display)); 636 else if (display != null) 637 res = new ValidationResult(new ConceptDefinitionComponent().setDisplay(display)); 638 else 639 res = new ValidationResult(new ConceptDefinitionComponent()); 640 saveToCache(res, cacheName); 641 return res; 642 } 643 644 645 protected void tlog(String msg) { 646 System.out.println("-tx: "+msg); 647 } 648 649 @SuppressWarnings("rawtypes") 650 private String describeValidationParameters(Parameters pin) { 651 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 652 for (ParametersParameterComponent p : pin.getParameter()) { 653 if (p.hasValue() && p.getValue() instanceof PrimitiveType) { 654 b.append(p.getName()+"="+((PrimitiveType) p.getValue()).asStringValue()); 655 } else if (p.hasValue() && p.getValue() instanceof Coding) { 656 b.append("system="+((Coding) p.getValue()).getSystem()); 657 b.append("code="+((Coding) p.getValue()).getCode()); 658 b.append("display="+((Coding) p.getValue()).getDisplay()); 659 } else if (p.hasValue() && p.getValue() instanceof CodeableConcept) { 660 if (((CodeableConcept) p.getValue()).hasCoding()) { 661 Coding c = ((CodeableConcept) p.getValue()).getCodingFirstRep(); 662 b.append("system="+c.getSystem()); 663 b.append("code="+c.getCode()); 664 b.append("display="+c.getDisplay()); 665 } else if (((CodeableConcept) p.getValue()).hasText()) { 666 b.append("text="+((CodeableConcept) p.getValue()).getText()); 667 } 668 } else if (p.hasResource() && (p.getResource() instanceof ValueSet)) { 669 b.append("valueset="+getVSSummary((ValueSet) p.getResource())); 670 } 671 } 672 return b.toString(); 673 } 674 675 private ValidationResult loadFromCache(String fn) throws FileNotFoundException, IOException { 676 if (fn == null) 677 return null; 678 if (!(new File(fn).exists())) 679 return null; 680 String cnt = TextFile.fileToString(fn); 681 if (cnt.startsWith("!error: ")) 682 return new ValidationResult(IssueSeverity.ERROR, cnt.substring(8)); 683 else if (cnt.startsWith("!warning: ")) 684 return new ValidationResult(IssueSeverity.ERROR, cnt.substring(10)); 685 else 686 return new ValidationResult(new ConceptDefinitionComponent().setDisplay(cnt)); 687 } 688 689 private void saveToCache(ValidationResult res, String cacheName) throws IOException { 690 if (cacheName == null) 691 return; 692 if (res.getDisplay() != null) 693 TextFile.stringToFile(res.getDisplay(), cacheName); 694 else if (res.getMessage() != null) { 695 if (res.getSeverity() == IssueSeverity.WARNING) 696 TextFile.stringToFile("!warning: "+res.getMessage(), cacheName); 697 else 698 TextFile.stringToFile("!error: "+res.getMessage(), cacheName); 699 } 700 } 701 702 private String generateCacheName(Parameters pin) throws IOException { 703 if (cache == null) 704 return null; 705 String json = new JsonParser().composeString(pin); 706 return Utilities.path(cache, "vc"+Integer.toString(json.hashCode())+".json"); 707 } 708 709 @Override 710 public ValueSetExpansionComponent expandVS(ConceptSetComponent inc, boolean heirachical) throws TerminologyServiceException { 711 ValueSet vs = new ValueSet(); 712 vs.setCompose(new ValueSetComposeComponent()); 713 vs.getCompose().getInclude().add(inc); 714 ValueSetExpansionOutcome vse = expandVS(vs, true, heirachical); 715 ValueSet valueset = vse.getValueset(); 716 if (valueset == null) 717 throw new TerminologyServiceException("Error Expanding ValueSet: "+vse.getError()); 718 return valueset.getExpansion(); 719 } 720 721 @Override 722 public ValidationResult validateCode(String system, String code, String display) { 723 try { 724 CodeSystem cs = null; 725 synchronized (lock) { 726 cs = codeSystems.get(system); 727 } 728 729 if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) 730 return verifyCodeInCodeSystem(cs, system, code, display); 731 else 732 return verifyCodeExternal(null, new Coding().setSystem(system).setCode(code).setDisplay(display), false); 733 } catch (Exception e) { 734 return new ValidationResult(IssueSeverity.FATAL, "Error validating code \""+code+"\" in system \""+system+"\": "+e.getMessage()); 735 } 736 } 737 738 739 @Override 740 public ValidationResult validateCode(Coding code, ValueSet vs) { 741 CodeSystem cs = null; 742 synchronized (lock) { 743 cs = codeSystems.get(code.getSystem()); 744 } 745 if (cs != null) 746 try { 747 return verifyCodeInCodeSystem(cs, code.getSystem(), code.getCode(), code.getDisplay()); 748 } catch (Exception e) { 749 return new ValidationResult(IssueSeverity.FATAL, "Error validating code \""+code+"\" in system \""+code.getSystem()+"\": "+e.getMessage()); 750 } 751 else if (vs != null && vs.hasExpansion()) 752 try { 753 return verifyCodeInternal(vs, code.getSystem(), code.getCode(), code.getDisplay()); 754 } catch (Exception e) { 755 return new ValidationResult(IssueSeverity.FATAL, "Error validating code \""+code+"\" in system \""+code.getSystem()+"\": "+e.getMessage()); 756 } 757 else 758 try { 759 return verifyCodeExternal(vs, code, true); 760 } catch (Exception e) { 761 return new ValidationResult(IssueSeverity.WARNING, "Error validating code \""+code+"\" in system \""+code.getSystem()+"\": "+e.getMessage()); 762 } 763 } 764 765 @Override 766 public ValidationResult validateCode(CodeableConcept code, ValueSet vs) { 767 try { 768 if (vs.hasExpansion()) 769 return verifyCodeInternal(vs, code); 770 else { 771 // we'll try expanding first; if that doesn't work, then we'll just pass it to the server to validate 772 // ... could be a problem if the server doesn't have the code systems we have locally, so we try not to depend on the server 773 try { 774 ValueSetExpansionOutcome vse = expandVS(vs, true, false); 775 if (vse.getValueset() != null && !hasTooCostlyExpansion(vse.getValueset())) 776 return verifyCodeInternal(vse.getValueset(), code); 777 } catch (Exception e) { 778 // failed? we'll just try the server 779 } 780 return verifyCodeExternal(vs, code, true); 781 } 782 } catch (Exception e) { 783 return new ValidationResult(IssueSeverity.FATAL, "Error validating code \""+code.toString()+"\": "+e.getMessage(), TerminologyServiceErrorClass.SERVER_ERROR); 784 } 785 } 786 787 788 private boolean hasTooCostlyExpansion(ValueSet valueset) { 789 return valueset != null && valueset.hasExpansion() && ToolingExtensions.hasExtension(valueset.getExpansion(), "http://hl7.org/fhir/StructureDefinition/valueset-toocostly"); 790 } 791 792 @Override 793 public ValidationResult validateCode(String system, String code, String display, ValueSet vs) { 794 try { 795 if (system == null && display == null) 796 return verifyCodeInternal(vs, code); 797 CodeSystem cs = null; 798 synchronized (lock) { 799 cs = codeSystems.get(system); 800 } 801 802 if (cs != null || vs.hasExpansion()) 803 return verifyCodeInternal(vs, system, code, display); 804 else 805 return verifyCodeExternal(vs, new Coding().setSystem(system).setCode(code).setDisplay(display), true); 806 } catch (Exception e) { 807 return new ValidationResult(IssueSeverity.FATAL, "Error validating code \""+code+"\" in system \""+system+"\": "+e.getMessage(), TerminologyServiceErrorClass.SERVER_ERROR); 808 } 809 } 810 811 @Override 812 public ValidationResult validateCode(String system, String code, String display, ConceptSetComponent vsi) { 813 try { 814 ValueSet vs = new ValueSet(); 815 vs.setUrl(Utilities.makeUuidUrn()); 816 vs.getCompose().addInclude(vsi); 817 return verifyCodeExternal(vs, new Coding().setSystem(system).setCode(code).setDisplay(display), true); 818 } catch (Exception e) { 819 return new ValidationResult(IssueSeverity.FATAL, "Error validating code \""+code+"\" in system \""+system+"\": "+e.getMessage()); 820 } 821 } 822 823 public void initTS(String cachePath) throws Exception { 824 cache = cachePath; 825 expansionCache = new ValueSetExpansionCache(this, null, lock); 826 validationCachePath = Utilities.path(cachePath, "validation.cache"); 827 try { 828 loadValidationCache(); 829 } catch (Exception e) { 830 e.printStackTrace(); 831 } 832 } 833 834 protected void loadValidationCache() throws JsonSyntaxException, Exception { 835 } 836 837 @Override 838 public List<ConceptMap> findMapsForSource(String url) throws FHIRException { 839 synchronized (lock) { 840 List<ConceptMap> res = new ArrayList<ConceptMap>(); 841 for (ConceptMap map : maps.values()) 842 if (((Reference) map.getSource()).getReference().equals(url)) 843 res.add(map); 844 return res; 845 } 846 } 847 848 private ValidationResult verifyCodeInternal(ValueSet vs, CodeableConcept code) throws Exception { 849 for (Coding c : code.getCoding()) { 850 ValidationResult res = verifyCodeInternal(vs, c.getSystem(), c.getCode(), c.getDisplay()); 851 if (res.isOk()) 852 return res; 853 } 854 if (code.getCoding().isEmpty()) 855 return new ValidationResult(IssueSeverity.ERROR, "None code provided"); 856 else 857 return new ValidationResult(IssueSeverity.ERROR, "None of the codes are in the specified value set"); 858 } 859 860 private ValidationResult verifyCodeInternal(ValueSet vs, String system, String code, String display) throws Exception { 861 if (vs.hasExpansion()) 862 return verifyCodeInExpansion(vs, system, code, display); 863 else { 864 ValueSetExpansionOutcome vse = expansionCache.getExpander().expand(vs, null); 865 if (vse.getValueset() != null) 866 return verifyCodeExternal(vs, new Coding().setSystem(system).setCode(code).setDisplay(display), false); 867 else 868 return verifyCodeInExpansion(vse.getValueset(), system, code, display); 869 } 870 } 871 872 private ValidationResult verifyCodeInternal(ValueSet vs, String code) throws FileNotFoundException, ETooCostly, IOException, FHIRException { 873 if (vs.hasExpansion()) 874 return verifyCodeInExpansion(vs, code); 875 else { 876 ValueSetExpansionOutcome vse = expansionCache.getExpander().expand(vs, null); 877 if (vse.getValueset() == null) 878 return new ValidationResult(IssueSeverity.ERROR, vse.getError(), vse.getErrorClass()); 879 else 880 return verifyCodeInExpansion(vse.getValueset(), code); 881 } 882 } 883 884 private ValidationResult verifyCodeInCodeSystem(CodeSystem cs, String system, String code, String display) throws Exception { 885 ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code); 886 if (cc == null) 887 if (cs.getContent().equals(CodeSystem.CodeSystemContentMode.COMPLETE)) 888 return new ValidationResult(IssueSeverity.ERROR, "Unknown Code "+code+" in "+cs.getUrl()); 889 else if (!cs.getContent().equals(CodeSystem.CodeSystemContentMode.NOTPRESENT)) 890 return new ValidationResult(IssueSeverity.WARNING, "Unknown Code "+code+" in partial code list of "+cs.getUrl()); 891 else 892 return verifyCodeExternal(null, new Coding().setSystem(system).setCode(code).setDisplay(display), false); 893 // 894 // return new ValidationResult(IssueSeverity.WARNING, "A definition was found for "+cs.getUrl()+", but it has no codes in the definition"); 895 // return new ValidationResult(IssueSeverity.ERROR, "Unknown Code "+code+" in "+cs.getUrl()); 896 if (display == null) 897 return new ValidationResult(cc); 898 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 899 if (cc.hasDisplay()) { 900 b.append(cc.getDisplay()); 901 if (display.equalsIgnoreCase(cc.getDisplay())) 902 return new ValidationResult(cc); 903 } 904 for (ConceptDefinitionDesignationComponent ds : cc.getDesignation()) { 905 b.append(ds.getValue()); 906 if (display.equalsIgnoreCase(ds.getValue())) 907 return new ValidationResult(cc); 908 } 909 return new ValidationResult(IssueSeverity.WARNING, "Display Name for "+code+" must be one of '"+b.toString()+"'", cc); 910 } 911 912 913 private ValidationResult verifyCodeInExpansion(ValueSet vs, String system,String code, String display) { 914 ValueSetExpansionContainsComponent cc = findCode(vs.getExpansion().getContains(), code, system); 915 if (cc == null) 916 return new ValidationResult(IssueSeverity.ERROR, "Unknown Code "+code+" in "+vs.getUrl()); 917 if (display == null) 918 return new ValidationResult(new ConceptDefinitionComponent().setCode(code).setDisplay(cc.getDisplay())); 919 if (cc.hasDisplay()) { 920 if (display.equalsIgnoreCase(cc.getDisplay())) 921 return new ValidationResult(new ConceptDefinitionComponent().setCode(code).setDisplay(cc.getDisplay())); 922 return new ValidationResult(IssueSeverity.WARNING, "Display Name for "+code+" must be '"+cc.getDisplay()+"'", new ConceptDefinitionComponent().setCode(code).setDisplay(cc.getDisplay())); 923 } 924 return null; 925 } 926 927 private ValidationResult verifyCodeInExpansion(ValueSet vs, String code) throws FHIRException { 928 if (vs.getExpansion().hasExtension("http://hl7.org/fhir/StructureDefinition/valueset-toocostly")) { 929 throw new FHIRException("Unable to validate core - value set is too costly to expand"); 930 } else { 931 ValueSetExpansionContainsComponent cc = findCode(vs.getExpansion().getContains(), code, null); 932 if (cc == null) 933 return new ValidationResult(IssueSeverity.ERROR, "Unknown Code "+code+" in "+vs.getUrl()); 934 return null; 935 } 936 } 937 938 private ValueSetExpansionContainsComponent findCode(List<ValueSetExpansionContainsComponent> contains, String code, String system) { 939 for (ValueSetExpansionContainsComponent cc : contains) { 940 if (code.equals(cc.getCode()) && (system == null || cc.getSystem().equals(system))) 941 return cc; 942 ValueSetExpansionContainsComponent c = findCode(cc.getContains(), code, system); 943 if (c != null) 944 return c; 945 } 946 return null; 947 } 948 949 private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code) { 950 for (ConceptDefinitionComponent cc : concept) { 951 if (code.equals(cc.getCode())) 952 return cc; 953 ConceptDefinitionComponent c = findCodeInConcept(cc.getConcept(), code); 954 if (c != null) 955 return c; 956 } 957 return null; 958 } 959 960 public boolean isCanRunWithoutTerminology() { 961 return canRunWithoutTerminology; 962 } 963 964 public void setCanRunWithoutTerminology(boolean canRunWithoutTerminology) { 965 this.canRunWithoutTerminology = canRunWithoutTerminology; 966 } 967 968 public int getExpandCodesLimit() { 969 return expandCodesLimit; 970 } 971 972 public void setExpandCodesLimit(int expandCodesLimit) { 973 this.expandCodesLimit = expandCodesLimit; 974 } 975 976 public void setLogger(ILoggingService logger) { 977 this.logger = logger; 978 } 979 980 public ExpansionProfile getExpansionProfile() { 981 return expProfile; 982 } 983 984 public void setExpansionProfile(ExpansionProfile expProfile) { 985 this.expProfile = expProfile; 986 } 987 988 @Override 989 public boolean isNoTerminologyServer() { 990 return noTerminologyServer; 991 } 992 993 public String getName() { 994 return name; 995 } 996 997 public void setName(String name) { 998 this.name = name; 999 } 1000 1001 @Override 1002 public Set<String> getResourceNamesAsSet() { 1003 Set<String> res = new HashSet<String>(); 1004 res.addAll(getResourceNames()); 1005 return res; 1006 } 1007 1008 public boolean isAllowLoadingDuplicates() { 1009 return allowLoadingDuplicates; 1010 } 1011 1012 public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) { 1013 this.allowLoadingDuplicates = allowLoadingDuplicates; 1014 } 1015 1016 @SuppressWarnings("unchecked") 1017 @Override 1018 public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException { 1019 if (class_ == StructureDefinition.class) 1020 uri = ProfileUtilities.sdNs(uri); 1021 synchronized (lock) { 1022 1023 if (uri.startsWith("http:") || uri.startsWith("https:")) { 1024 String version = null; 1025 if (uri.contains("#")) 1026 uri = uri.substring(0, uri.indexOf("#")); 1027 if (class_ == Resource.class || class_ == null) { 1028 if (structures.containsKey(uri)) 1029 return (T) structures.get(uri); 1030 if (valueSets.containsKey(uri)) 1031 return (T) valueSets.get(uri); 1032 if (codeSystems.containsKey(uri)) 1033 return (T) codeSystems.get(uri); 1034 if (operations.containsKey(uri)) 1035 return (T) operations.get(uri); 1036 if (searchParameters.containsKey(uri)) 1037 return (T) searchParameters.get(uri); 1038 if (maps.containsKey(uri)) 1039 return (T) maps.get(uri); 1040 if (transforms.containsKey(uri)) 1041 return (T) transforms.get(uri); 1042 if (questionnaires.containsKey(uri)) 1043 return (T) questionnaires.get(uri); 1044 return null; 1045 } else if (class_ == StructureDefinition.class) { 1046 return (T) structures.get(uri); 1047 } else if (class_ == ValueSet.class) { 1048 return (T) valueSets.get(uri); 1049 } else if (class_ == CodeSystem.class) { 1050 return (T) codeSystems.get(uri); 1051 } else if (class_ == ConceptMap.class) { 1052 return (T) maps.get(uri); 1053 } else if (class_ == OperationDefinition.class) { 1054 OperationDefinition od = operations.get(uri); 1055 return (T) od; 1056 } else if (class_ == SearchParameter.class) { 1057 SearchParameter res = searchParameters.get(uri); 1058 if (res == null) { 1059 StringBuilder b = new StringBuilder(); 1060 for (String s : searchParameters.keySet()) { 1061 b.append(s); 1062 b.append("\r\n"); 1063 } 1064 } 1065 if (res != null) 1066 return (T) res; 1067 } 1068 } 1069 if (class_ == Questionnaire.class) 1070 return (T) questionnaires.get(uri); 1071 if (class_ == null) { 1072 if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) 1073 return null; 1074 1075 // it might be a special URL. 1076 if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) { 1077 Resource res = findTxValueSet(uri); 1078 if (res != null) 1079 return (T) res; 1080 } 1081 return null; 1082 } 1083 throw new FHIRException("not done yet: can't fetch "+uri); 1084 } 1085 } 1086 1087 private Set<String> notCanonical = new HashSet<String>(); 1088 1089 private MetadataResource findTxValueSet(String uri) { 1090 MetadataResource res = expansionCache.getStoredResource(uri); 1091 if (res != null) 1092 return res; 1093 synchronized (lock) { 1094 if (notCanonical.contains(uri)) 1095 return null; 1096 } 1097 try { 1098 tlog("get canonical "+uri); 1099 res = txServer.getCanonical(ValueSet.class, uri); 1100 } catch (Exception e) { 1101 synchronized (lock) { 1102 notCanonical.add(uri); 1103 } 1104 return null; 1105 } 1106 if (res != null) 1107 try { 1108 expansionCache.storeResource(res); 1109 } catch (IOException e) { 1110 } 1111 return res; 1112 } 1113 1114 @Override 1115 public Resource fetchResourceById(String type, String uri) { 1116 synchronized (lock) { 1117 String[] parts = uri.split("\\/"); 1118 if (!Utilities.noString(type) && parts.length == 1) 1119 return allResourcesById.get(type).get(parts[0]); 1120 if (parts.length >= 2) { 1121 if (!Utilities.noString(type)) 1122 if (!type.equals(parts[parts.length-2])) 1123 throw new Error("Resource type mismatch for "+type+" / "+uri); 1124 return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]); 1125 } else 1126 throw new Error("Unable to process request for resource for "+type+" / "+uri); 1127 } 1128 } 1129 1130 public <T extends Resource> T fetchResource(Class<T> class_, String uri) { 1131 try { 1132 return fetchResourceWithException(class_, uri); 1133 } catch (FHIRException e) { 1134 throw new Error(e); 1135 } 1136 } 1137 1138 @Override 1139 public <T extends Resource> boolean hasResource(Class<T> class_, String uri) { 1140 try { 1141 return fetchResourceWithException(class_, uri) != null; 1142 } catch (Exception e) { 1143 return false; 1144 } 1145 } 1146 1147 @Override 1148 public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException { 1149 ValueSet vs = null; 1150 if (binding.hasValueSetCanonicalType()) { 1151 vs = fetchResource(ValueSet.class, binding.getValueSetCanonicalType().getValue()); 1152 if (vs == null) 1153 throw new FHIRException("Unable to resolve value Set "+binding.getValueSetCanonicalType().getValue()); 1154 } else { 1155 vs = fetchResource(ValueSet.class, binding.getValueSetUriType().asStringValue()); 1156 if (vs == null) 1157 throw new FHIRException("Unable to resolve value Set "+binding.getValueSetUriType().asStringValue()); 1158 } 1159 return expandVS(vs, cacheOk, heirarchical); 1160 } 1161 1162 1163 public TranslationServices translator() { 1164 return translator; 1165 } 1166 1167 public void setTranslator(TranslationServices translator) { 1168 this.translator = translator; 1169 } 1170 1171 public class NullTranslator implements TranslationServices { 1172 1173 @Override 1174 public String translate(String context, String value, String targetLang) { 1175 return value; 1176 } 1177 1178 @Override 1179 public String translate(String context, String value) { 1180 return value; 1181 } 1182 1183 @Override 1184 public String toStr(float value) { 1185 return null; 1186 } 1187 1188 @Override 1189 public String toStr(Date value) { 1190 return null; 1191 } 1192 1193 @Override 1194 public String translateAndFormat(String contest, String lang, String value, Object... args) { 1195 return String.format(value, args); 1196 } 1197 1198 @Override 1199 public Map<String, String> translations(String value) { 1200 // TODO Auto-generated method stub 1201 return null; 1202 } 1203 1204 @Override 1205 public Set<String> listTranslations(String category) { 1206 // TODO Auto-generated method stub 1207 return null; 1208 } 1209 1210 } 1211 1212 public void reportStatus(JsonObject json) { 1213 synchronized (lock) { 1214 json.addProperty("codeystem-count", codeSystems.size()); 1215 json.addProperty("valueset-count", valueSets.size()); 1216 json.addProperty("conceptmap-count", maps.size()); 1217 json.addProperty("transforms-count", transforms.size()); 1218 json.addProperty("structures-count", structures.size()); 1219 } 1220 } 1221 1222 1223 public void dropResource(Resource r) throws FHIRException { 1224 dropResource(r.fhirType(), r.getId()); 1225 } 1226 1227 public void dropResource(String fhirType, String id) { 1228 synchronized (lock) { 1229 1230 Map<String, Resource> map = allResourcesById.get(fhirType); 1231 if (map == null) { 1232 map = new HashMap<String, Resource>(); 1233 allResourcesById.put(fhirType, map); 1234 } 1235 if (map.containsKey(id)) 1236 map.remove(id); 1237 1238 if (fhirType.equals("StructureDefinition")) 1239 dropMetadataResource(structures, id); 1240 else if (fhirType.equals("ValueSet")) 1241 dropMetadataResource(valueSets, id); 1242 else if (fhirType.equals("CodeSystem")) 1243 dropMetadataResource(codeSystems, id); 1244 else if (fhirType.equals("OperationDefinition")) 1245 dropMetadataResource(operations, id); 1246 else if (fhirType.equals("Questionnaire")) 1247 dropMetadataResource(questionnaires, id); 1248 else if (fhirType.equals("ConceptMap")) 1249 dropMetadataResource(maps, id); 1250 else if (fhirType.equals("StructureMap")) 1251 dropMetadataResource(transforms, id); 1252 else if (fhirType.equals("NamingSystem")) 1253 for (int i = systems.size()-1; i >= 0; i--) { 1254 if (systems.get(i).getId().equals(id)) 1255 systems.remove(i); 1256 } 1257 } 1258 } 1259 1260 private <T extends MetadataResource> void dropMetadataResource(Map<String, T> map, String id) { 1261 T res = map.get(id); 1262 if (res != null) { 1263 map.remove(id); 1264 if (map.containsKey(res.getUrl())) 1265 map.remove(res.getUrl()); 1266 if (res.getVersion() != null) 1267 if (map.containsKey(res.getUrl()+"|"+res.getVersion())) 1268 map.remove(res.getUrl()+"|"+res.getVersion()); 1269 } 1270 } 1271 1272 @Override 1273 public List<MetadataResource> allConformanceResources() { 1274 synchronized (lock) { 1275 List<MetadataResource> result = new ArrayList<MetadataResource>(); 1276 result.addAll(structures.values()); 1277 result.addAll(codeSystems.values()); 1278 result.addAll(valueSets.values()); 1279 result.addAll(maps.values()); 1280 result.addAll(transforms.values()); 1281 return result; 1282 } 1283 } 1284 1285 public void addNonSupportedCodeSystems(String s) { 1286 synchronized (lock) { 1287 nonSupportedCodeSystems.add(s); 1288 } 1289 } 1290 1291 public String listNonSupportedSystems() { 1292 synchronized (lock) { 1293 String sl = null; 1294 for (String s : nonSupportedCodeSystems) 1295 sl = sl == null ? s : sl + "\r\n" + s; 1296 return sl; 1297 } 1298 } 1299 1300 1301 public int totalCount() { 1302 synchronized (lock) { 1303 return valueSets.size() + maps.size() + structures.size() + transforms.size(); 1304 } 1305 } 1306 1307 public List<ConceptMap> listMaps() { 1308 List<ConceptMap> m = new ArrayList<ConceptMap>(); 1309 synchronized (lock) { 1310 m.addAll(maps.values()); 1311 } 1312 return m; 1313 } 1314 1315 public List<StructureMap> listTransforms() { 1316 List<StructureMap> m = new ArrayList<StructureMap>(); 1317 synchronized (lock) { 1318 m.addAll(transforms.values()); 1319 } 1320 return m; 1321 } 1322 1323 public StructureMap getTransform(String code) { 1324 synchronized (lock) { 1325 return transforms.get(code); 1326 } 1327 } 1328 1329 public List<StructureDefinition> listStructures() { 1330 List<StructureDefinition> m = new ArrayList<StructureDefinition>(); 1331 synchronized (lock) { 1332 m.addAll(structures.values()); 1333 } 1334 return m; 1335 } 1336 1337 public StructureDefinition getStructure(String code) { 1338 synchronized (lock) { 1339 return structures.get(code); 1340 } 1341 } 1342 1343 public void setCache(ValueSetExpansionCache cache) { 1344 synchronized (lock) { 1345 this.expansionCache = cache; 1346 } 1347 } 1348 1349 @Override 1350 public String oid2Uri(String oid) { 1351 synchronized (lock) { 1352 String uri = OIDUtils.getUriForOid(oid); 1353 if (uri != null) 1354 return uri; 1355 for (NamingSystem ns : systems) { 1356 if (hasOid(ns, oid)) { 1357 uri = getUri(ns); 1358 if (uri != null) 1359 return null; 1360 } 1361 } 1362 } 1363 return null; 1364 } 1365 1366 1367 private String getUri(NamingSystem ns) { 1368 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 1369 if (id.getType() == NamingSystemIdentifierType.URI) 1370 return id.getValue(); 1371 } 1372 return null; 1373 } 1374 1375 private boolean hasOid(NamingSystem ns, String oid) { 1376 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 1377 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) 1378 return true; 1379 } 1380 return false; 1381 } 1382 1383 public void cacheVS(JsonObject json, Map<String, ValidationResult> t) { 1384 synchronized (lock) { 1385 validationCache.put(json.get("url").getAsString(), t); 1386 } 1387 } 1388 1389 public SearchParameter getSearchParameter(String code) { 1390 synchronized (lock) { 1391 return searchParameters.get(code); 1392 } 1393 } 1394 1395 1396}