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