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