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