
001package org.hl7.fhir.r5.context; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Map; 008import java.util.Set; 009 010import lombok.extern.slf4j.Slf4j; 011import org.apache.commons.lang3.exception.ExceptionUtils; 012import org.hl7.fhir.exceptions.DefinitionException; 013import org.hl7.fhir.exceptions.FHIRException; 014import org.hl7.fhir.r5.conformance.profile.BindingResolution; 015import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; 016import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 017import org.hl7.fhir.r5.model.CanonicalResource; 018import org.hl7.fhir.r5.model.CodeSystem; 019import org.hl7.fhir.r5.model.ElementDefinition; 020import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 021import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 022import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 023import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType; 024import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent; 025import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; 026import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 027import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 028import org.hl7.fhir.r5.model.StructureMap; 029import org.hl7.fhir.r5.utils.ToolingExtensions; 030import org.hl7.fhir.r5.utils.UserDataNames; 031import org.hl7.fhir.r5.utils.XVerExtensionManager; 032import org.hl7.fhir.r5.model.Identifier; 033import org.hl7.fhir.r5.model.NamingSystem; 034import org.hl7.fhir.r5.model.Parameters; 035import org.hl7.fhir.r5.model.Resource; 036import org.hl7.fhir.r5.model.StructureDefinition; 037import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 038import org.hl7.fhir.utilities.OIDUtilities; 039import org.hl7.fhir.utilities.Utilities; 040import org.hl7.fhir.utilities.VersionUtilities; 041import org.hl7.fhir.utilities.i18n.I18nConstants; 042import org.hl7.fhir.utilities.validation.ValidationMessage; 043import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 044import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 045 046@MarkedToMoveToAdjunctPackage 047@Slf4j 048public class ContextUtilities implements ProfileKnowledgeProvider { 049 050 private IWorkerContext context; 051 private boolean suppressDebugMessages; 052 private XVerExtensionManager xverManager; 053 private Map<String, String> oidCache = new HashMap<>(); 054 private List<StructureDefinition> allStructuresList = new ArrayList<StructureDefinition>(); 055 private List<String> canonicalResourceNames; 056 private List<String> concreteResourceNames; 057 private Set<String> concreteResourceNameSet; 058 private List<String> suppressedMappings; 059 private Set<String> localFileNames; 060 private Set<String> masterSourceNames; 061 062 public ContextUtilities(IWorkerContext context) { 063 super(); 064 this.context = context; 065 this.suppressedMappings = new ArrayList<String>(); 066 } 067 068 public ContextUtilities(IWorkerContext context, List<String> suppressedMappings) { 069 super(); 070 this.context = context; 071 this.suppressedMappings = suppressedMappings; 072 } 073 074 @Deprecated 075 public boolean isSuppressDebugMessages() { 076 return false; 077 } 078 079 @Deprecated 080 public void setSuppressDebugMessages(boolean suppressDebugMessages) { 081 //DO NOTHING 082 } 083 084 public String oid2Uri(String oid) { 085 if (oid != null && oid.startsWith("urn:oid:")) { 086 oid = oid.substring(8); 087 } 088 if (oidCache.containsKey(oid)) { 089 return oidCache.get(oid); 090 } 091 092 String uri = OIDUtilities.getUriForOid(oid); 093 if (uri != null) { 094 oidCache.put(oid, uri); 095 return uri; 096 } 097 CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables"); 098 if (cs != null) { 099 for (ConceptDefinitionComponent cc : cs.getConcept()) { 100 for (ConceptPropertyComponent cp : cc.getProperty()) { 101 if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) { 102 for (ConceptPropertyComponent cp2 : cc.getProperty()) { 103 if ("v2-cs-uri".equals(cp2.getCode())) { 104 oidCache.put(oid, cp2.getValue().primitiveValue()); 105 return cp2.getValue().primitiveValue(); 106 } 107 } 108 } 109 } 110 } 111 } 112 for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) { 113 if (("urn:oid:"+oid).equals(css.getUrl())) { 114 oidCache.put(oid, css.getUrl()); 115 return css.getUrl(); 116 } 117 for (Identifier id : css.getIdentifier()) { 118 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) { 119 oidCache.put(oid, css.getUrl()); 120 return css.getUrl(); 121 } 122 } 123 } 124 for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) { 125 if (hasOid(ns, oid)) { 126 uri = getUri(ns); 127 if (uri != null) { 128 oidCache.put(oid, null); 129 return null; 130 } 131 } 132 } 133 oidCache.put(oid, null); 134 return null; 135 } 136 137 private String getUri(NamingSystem ns) { 138 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 139 if (id.getType() == NamingSystemIdentifierType.URI) 140 return id.getValue(); 141 } 142 return null; 143 } 144 145 private boolean hasOid(NamingSystem ns, String oid) { 146 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 147 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) 148 return true; 149 } 150 return false; 151 } 152 153 /** 154 * @return a list of the resource and type names defined for this version 155 */ 156 public List<String> getTypeNames() { 157 Set<String> result = new HashSet<String>(); 158 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 159 if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 160 result.add(sd.getName()); 161 } 162 return Utilities.sorted(result); 163 } 164 165 166 /** 167 * @return a set of the resource and type names defined for this version 168 */ 169 public Set<String> getTypeNameSet() { 170 Set<String> result = new HashSet<String>(); 171 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 172 if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && 173 VersionUtilities.versionsCompatible(context.getVersion(), sd.getFhirVersion().toCode())) { 174 result.add(sd.getName()); 175 } 176 } 177 return result; 178 } 179 180 public String getLinkForUrl(String corePath, String url) { 181 if (url == null) { 182 return null; 183 } 184 185 if (context.hasResource(CanonicalResource.class, url)) { 186 CanonicalResource cr = context.fetchResource(CanonicalResource.class, url); 187 return cr.getWebPath(); 188 } 189 return null; 190 } 191 192 193 protected String tail(String url) { 194 if (Utilities.noString(url)) { 195 return "noname"; 196 } 197 if (url.contains("/")) { 198 return url.substring(url.lastIndexOf("/")+1); 199 } 200 return url; 201 } 202 203 private boolean hasUrlProperty(StructureDefinition sd) { 204 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 205 if (ed.getPath().equals(sd.getType()+".url")) { 206 return true; 207 } 208 } 209 return false; 210 } 211 212 // -- profile services --------------------------------------------------------- 213 214 215 /** 216 * @return a list of the resource names that are canonical resources defined for this version 217 */ 218 public List<String> getCanonicalResourceNames() { 219 if (canonicalResourceNames == null) { 220 canonicalResourceNames = new ArrayList<>(); 221 Set<String> names = new HashSet<>(); 222 for (StructureDefinition sd : allStructures()) { 223 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) { 224 names.add(sd.getType()); 225 } 226 } 227 canonicalResourceNames.addAll(Utilities.sorted(names)); 228 } 229 return canonicalResourceNames; 230 } 231 232 /** 233 * @return a list of all structure definitions, with snapshots generated (if possible) 234 */ 235 public List<StructureDefinition> allStructures(){ 236 if (allStructuresList.isEmpty()) { 237 Set<StructureDefinition> set = new HashSet<StructureDefinition>(); 238 for (StructureDefinition sd : getStructures()) { 239 if (!set.contains(sd)) { 240 try { 241 generateSnapshot(sd); 242 // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd); 243 } catch (Exception e) { 244 log.debug("Unable to generate snapshot @2 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage()); 245 context.getLogger().logDebugMessage(ILoggingService.LogCategory.GENERATE, ExceptionUtils.getStackTrace(e)); 246 } 247 allStructuresList.add(sd); 248 set.add(sd); 249 } 250 } 251 } 252 return allStructuresList; 253 } 254 255 /** 256 * @return a list of all structure definitions, without trying to generate snapshots 257 */ 258 public List<StructureDefinition> getStructures() { 259 return context.fetchResourcesByType(StructureDefinition.class); 260 } 261 262 /** 263 * Given a structure definition, generate a snapshot (or regenerate it) 264 * @param p 265 * @throws DefinitionException 266 * @throws FHIRException 267 */ 268 public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException { 269 if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p))) { 270 if (!p.hasBaseDefinition()) 271 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl())); 272 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p); 273 if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) { 274 sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion()); 275 } 276 if (sd == null) { 277 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition())); 278 } 279 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 280 List<String> errors = new ArrayList<String>(); 281 ProfileUtilities pu = new ProfileUtilities(context, msgs, this); 282 pu.setAutoFixSliceNames(true); 283 pu.setThrowException(false); 284 pu.setForPublication(context.isForPublication()); 285 pu.setSuppressedMappings(suppressedMappings); 286 pu.setLocalFileNames(localFileNames); 287 pu.setMasterSourceFileNames(masterSourceNames); 288 if (xverManager == null) { 289 xverManager = new XVerExtensionManager(context); 290 } 291 pu.setXver(xverManager); 292 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 293 pu.sortDifferential(sd, p, p.getUrl(), errors, true); 294 } 295 pu.setDebug(false); 296 for (String err : errors) { 297 msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getWebPath(), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); 298 } 299 pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString(UserDataNames.render_webroot), p.getName()); 300 for (ValidationMessage msg : msgs) { 301 if ((!ProfileUtilities.isSuppressIgnorableExceptions() && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) { 302 if (!msg.isIgnorableError()) { 303 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage())); 304 } else { 305 log.error(msg.getMessage()); 306 } 307 } 308 } 309 if (!p.hasSnapshot()) 310 throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl())); 311 pu = null; 312 } 313 p.setGeneratedSnapshot(true); 314 } 315 316 317 // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind. 318 private boolean isProfileNeedsRegenerate(StructureDefinition p) { 319 boolean needs = !p.hasUserData(UserDataNames.SNAPSHOT_regeneration_tracker) && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse"); 320 if (needs) { 321 p.setUserData(UserDataNames.SNAPSHOT_regeneration_tracker, "yes"); 322 } 323 return needs; 324 } 325 326 @Override 327 public boolean isPrimitiveType(String type) { 328 return context.isPrimitiveType(type); 329 } 330 331 @Override 332 public boolean isDatatype(String type) { 333 StructureDefinition sd = context.fetchTypeDefinition(type); 334 return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 335 } 336 337 @Override 338 public boolean isResource(String t) { 339 if (getConcreteResourceSet().contains(t)) { 340 return true; 341 } 342 StructureDefinition sd; 343 try { 344 sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t); 345 } catch (Exception e) { 346 return false; 347 } 348 if (sd == null) 349 return false; 350 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) 351 return false; 352 return sd.getKind() == StructureDefinitionKind.RESOURCE; 353 } 354 355 @Override 356 public boolean hasLinkFor(String typeSimple) { 357 return false; 358 } 359 360 @Override 361 public String getLinkFor(String corePath, String typeSimple) { 362 return null; 363 } 364 365 @Override 366 public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) { 367 return null; 368 } 369 370 @Override 371 public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) { 372 return null; 373 } 374 375 @Override 376 public String getLinkForProfile(StructureDefinition profile, String url) { 377 return null; 378 } 379 @Override 380 public boolean prependLinks() { 381 return false; 382 } 383 384 public StructureDefinition fetchByJsonName(String key) { 385 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 386 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 387 if (/*sd.getKind() == StructureDefinitionKind.LOGICAL && */ 388 // this is turned off because it's valid to use a FHIR type directly in 389 // an extension of this kind, and that can't be a logical model. Any profile on 390 // a type is acceptable as long as it has the json name on it 391 ed != null && ed.hasExtension(ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED) && 392 key.equals(ToolingExtensions.readStringExtension(ed, ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_NAME_DEPRECATED))) { 393 return sd; 394 } 395 } 396 return null; 397 } 398 399 public Set<String> getConcreteResourceSet() { 400 if (concreteResourceNameSet == null) { 401 concreteResourceNameSet = new HashSet<>(); 402 for (StructureDefinition sd : getStructures()) { 403 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { 404 concreteResourceNameSet.add(sd.getType()); 405 } 406 } 407 } 408 return concreteResourceNameSet; 409 } 410 411 public List<String> getConcreteResources() { 412 if (concreteResourceNames == null) { 413 concreteResourceNames = new ArrayList<>(); 414 concreteResourceNames.addAll(Utilities.sorted(getConcreteResourceSet())); 415 } 416 return concreteResourceNames; 417 } 418 419 public List<StructureMap> listMaps(String url) { 420 List<StructureMap> res = new ArrayList<>(); 421 String start = url.substring(0, url.indexOf("*")); 422 String end = url.substring(url.indexOf("*")+1); 423 for (StructureMap map : context.fetchResourcesByType(StructureMap.class)) { 424 String u = map.getUrl(); 425 if (u.startsWith(start) && u.endsWith(end)) { 426 res.add(map); 427 } 428 } 429 return res; 430 } 431 432 public List<String> fetchCodeSystemVersions(String system) { 433 List<String> res = new ArrayList<>(); 434 for (CodeSystem cs : context.fetchResourcesByType(CodeSystem.class)) { 435 if (system.equals(cs.getUrl()) && cs.hasVersion()) { 436 res.add(cs.getVersion()); 437 } 438 } 439 return res; 440 } 441 442 public StructureDefinition findType(String typeName) { 443 StructureDefinition t = context.fetchTypeDefinition(typeName); 444 if (t != null) { 445 return t; 446 } 447 List<StructureDefinition> candidates = new ArrayList<>(); 448 for (StructureDefinition sd : getStructures()) { 449 if (sd.getType().equals(typeName)) { 450 candidates.add(sd); 451 } 452 } 453 if (candidates.size() == 1) { 454 return candidates.get(0); 455 } 456 return null; 457 } 458 459 public StructureDefinition fetchProfileByIdentifier(String tid) { 460 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 461 for (Identifier ii : sd.getIdentifier()) { 462 if (tid.equals(ii.getValue())) { 463 return sd; 464 } 465 } 466 } 467 return null; 468 } 469 470 public boolean isAbstractType(String typeName) { 471 StructureDefinition sd = context.fetchTypeDefinition(typeName); 472 if (sd != null) { 473 return sd.getAbstract(); 474 } 475 return false; 476 } 477 478 public boolean isDomainResource(String typeName) { 479 StructureDefinition sd = context.fetchTypeDefinition(typeName); 480 while (sd != null) { 481 if ("DomainResource".equals(sd.getType())) { 482 return true; 483 } 484 sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); 485 } 486 return false; 487 } 488 489 public IWorkerContext getWorker() { 490 return context; 491 } 492 493 @Override 494 public String getCanonicalForDefaultContext() { 495 // TODO Auto-generated method stub 496 return null; 497 } 498 499 public String pinValueSet(String valueSet) { 500 return pinValueSet(valueSet, context.getExpansionParameters()); 501 } 502 503 public String pinValueSet(String value, Parameters expParams) { 504 if (value.contains("|")) { 505 return value; 506 } 507 for (ParametersParameterComponent p : expParams.getParameter()) { 508 if ("default-valueset-version".equals(p.getName())) { 509 String s = p.getValue().primitiveValue(); 510 if (s.startsWith(value+"|")) { 511 return s; 512 } 513 } 514 } 515 return value; 516 } 517 518 public List<StructureDefinition> allBaseStructures() { 519 List<StructureDefinition> res = new ArrayList<>(); 520 for (StructureDefinition sd : allStructures()) { 521 if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() != StructureDefinitionKind.LOGICAL) { 522 res.add(sd); 523 } 524 } 525 return res; 526 } 527 528 public <T extends Resource> List<T> fetchByIdentifier(Class<T> class_, String system) { 529 List<T> list = new ArrayList<>(); 530 for (T t : context.fetchResourcesByType(class_)) { 531 if (t instanceof CanonicalResource) { 532 CanonicalResource cr = (CanonicalResource) t; 533 for (Identifier id : cr.getIdentifier()) { 534 if (system.equals(id.getValue())) { 535 list.add(t); 536 } 537 } 538 } 539 } 540 return list; 541 } 542 543 public StructureDefinition fetchStructureByName(String name) { 544 StructureDefinition sd = null; 545 for (StructureDefinition t : context.fetchResourcesByType(StructureDefinition.class)) { 546 if (name.equals(t.getName())) { 547 if (sd == null) { 548 sd = t; 549 } else { 550 throw new FHIRException("Duplicate Structure name "+name+": found both "+t.getVersionedUrl()+" and "+sd.getVersionedUrl()); 551 } 552 } 553 } 554 return sd; 555 } 556 557 @Override 558 public String getDefinitionsName(Resource r) { 559 // TODO Auto-generated method stub 560 return null; 561 } 562 563 public Set<String> getLocalFileNames() { 564 return localFileNames; 565 } 566 567 public void setLocalFileNames(Set<String> localFileNames) { 568 this.localFileNames = localFileNames; 569 } 570 571 public Set<String> getMasterSourceNames() { 572 return masterSourceNames; 573 } 574 575 public void setMasterSourceNames(Set<String> masterSourceNames) { 576 this.masterSourceNames = masterSourceNames; 577 } 578 579} 580