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