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