
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 boolean ignoreProfileErrors; 046 private XVerExtensionManager xverManager; 047 private Map<String, String> oidCache = new HashMap<>(); 048 private List<StructureDefinition> allStructuresList = new ArrayList<StructureDefinition>(); 049 private List<String> canonicalResourceNames; 050 private List<String> concreteResourceNames; 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 public boolean isIgnoreProfileErrors() { 065 return ignoreProfileErrors; 066 } 067 068 public void setIgnoreProfileErrors(boolean ignoreProfileErrors) { 069 this.ignoreProfileErrors = ignoreProfileErrors; 070 } 071 072 public String oid2Uri(String oid) { 073 if (oid != null && oid.startsWith("urn:oid:")) { 074 oid = oid.substring(8); 075 } 076 if (oidCache.containsKey(oid)) { 077 return oidCache.get(oid); 078 } 079 080 String uri = OIDUtils.getUriForOid(oid); 081 if (uri != null) { 082 oidCache.put(oid, uri); 083 return uri; 084 } 085 CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables"); 086 if (cs != null) { 087 for (ConceptDefinitionComponent cc : cs.getConcept()) { 088 for (ConceptPropertyComponent cp : cc.getProperty()) { 089 if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) { 090 for (ConceptPropertyComponent cp2 : cc.getProperty()) { 091 if ("v2-cs-uri".equals(cp2.getCode())) { 092 oidCache.put(oid, cp2.getValue().primitiveValue()); 093 return cp2.getValue().primitiveValue(); 094 } 095 } 096 } 097 } 098 } 099 } 100 for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) { 101 if (("urn:oid:"+oid).equals(css.getUrl())) { 102 oidCache.put(oid, css.getUrl()); 103 return css.getUrl(); 104 } 105 for (Identifier id : css.getIdentifier()) { 106 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) { 107 oidCache.put(oid, css.getUrl()); 108 return css.getUrl(); 109 } 110 } 111 } 112 for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) { 113 if (hasOid(ns, oid)) { 114 uri = getUri(ns); 115 if (uri != null) { 116 oidCache.put(oid, null); 117 return null; 118 } 119 } 120 } 121 oidCache.put(oid, null); 122 return null; 123 } 124 125 private String getUri(NamingSystem ns) { 126 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 127 if (id.getType() == NamingSystemIdentifierType.URI) 128 return id.getValue(); 129 } 130 return null; 131 } 132 133 private boolean hasOid(NamingSystem ns, String oid) { 134 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 135 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) 136 return true; 137 } 138 return false; 139 } 140 141 /** 142 * @return a list of the resource and type names defined for this version 143 */ 144 public List<String> getTypeNames() { 145 Set<String> result = new HashSet<String>(); 146 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 147 if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 148 result.add(sd.getName()); 149 } 150 return Utilities.sorted(result); 151 } 152 153 154 /** 155 * @return a set of the resource and type names defined for this version 156 */ 157 public Set<String> getTypeNameSet() { 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 VersionUtilities.versionsCompatible(context.getVersion(), sd.getFhirVersion().toCode())) { 162 result.add(sd.getName()); 163 } 164 } 165 return result; 166 } 167 168 public String getLinkForUrl(String corePath, String url) { 169 if (url == null) { 170 return null; 171 } 172 173 if (context.hasResource(CanonicalResource.class, url)) { 174 CanonicalResource cr = context.fetchResource(CanonicalResource.class, url); 175 return cr.getWebPath(); 176 } 177 return null; 178 } 179 180 181 protected String tail(String url) { 182 if (Utilities.noString(url)) { 183 return "noname"; 184 } 185 if (url.contains("/")) { 186 return url.substring(url.lastIndexOf("/")+1); 187 } 188 return url; 189 } 190 191 private boolean hasUrlProperty(StructureDefinition sd) { 192 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 193 if (ed.getPath().equals(sd.getType()+".url")) { 194 return true; 195 } 196 } 197 return false; 198 } 199 200 // -- profile services --------------------------------------------------------- 201 202 203 /** 204 * @return a list of the resource names that are canonical resources defined for this version 205 */ 206 public List<String> getCanonicalResourceNames() { 207 if (canonicalResourceNames == null) { 208 canonicalResourceNames = new ArrayList<>(); 209 Set<String> names = new HashSet<>(); 210 for (StructureDefinition sd : allStructures()) { 211 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) { 212 names.add(sd.getType()); 213 } 214 } 215 canonicalResourceNames.addAll(Utilities.sorted(names)); 216 } 217 return canonicalResourceNames; 218 } 219 220 /** 221 * @return a list of all structure definitions, with snapshots generated (if possible) 222 */ 223 public List<StructureDefinition> allStructures(){ 224 if (allStructuresList.isEmpty()) { 225 Set<StructureDefinition> set = new HashSet<StructureDefinition>(); 226 for (StructureDefinition sd : getStructures()) { 227 if (!set.contains(sd)) { 228 try { 229 generateSnapshot(sd); 230 // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd); 231 } catch (Exception e) { 232 if (!isSuppressDebugMessages()) { 233 System.out.println("Unable to generate snapshot @2 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage()); 234 if (context.getLogger().isDebugLogging()) { 235 e.printStackTrace(); 236 } 237 } 238 } 239 allStructuresList.add(sd); 240 set.add(sd); 241 } 242 } 243 } 244 return allStructuresList; 245 } 246 247 /** 248 * @return a list of all structure definitions, without trying to generate snapshots 249 */ 250 public List<StructureDefinition> getStructures() { 251 return context.fetchResourcesByType(StructureDefinition.class); 252 } 253 254 /** 255 * Given a structure definition, generate a snapshot (or regenerate it) 256 * @param p 257 * @throws DefinitionException 258 * @throws FHIRException 259 */ 260 public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException { 261 generateSnapshot(p, false); 262 } 263 264 public void generateSnapshot(StructureDefinition p, boolean ifLogical) { 265 if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p) ) && (ifLogical || p.getKind() != StructureDefinitionKind.LOGICAL)) { 266 if (!p.hasBaseDefinition()) 267 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl())); 268 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p); 269 if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) { 270 sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion()); 271 } 272 if (sd == null) { 273 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition())); 274 } 275 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 276 List<String> errors = new ArrayList<String>(); 277 ProfileUtilities pu = new ProfileUtilities(context, msgs, this); 278 pu.setAutoFixSliceNames(true); 279 pu.setThrowException(false); 280 pu.setForPublication(context.isForPublication()); 281 if (xverManager == null) { 282 xverManager = new XVerExtensionManager(context); 283 } 284 pu.setXver(xverManager); 285 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 286 pu.sortDifferential(sd, p, p.getUrl(), errors, true); 287 } 288 pu.setDebug(false); 289 for (String err : errors) { 290 msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getWebPath(), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); 291 } 292 pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName()); 293 for (ValidationMessage msg : msgs) { 294 if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) { 295 if (!msg.isIgnorableError()) { 296 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage())); 297 } else { 298 System.err.println(msg.getMessage()); 299 } 300 } 301 } 302 if (!p.hasSnapshot()) 303 throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl())); 304 pu = null; 305 } 306 p.setGeneratedSnapshot(true); 307 } 308 309 310 // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind. 311 private boolean isProfileNeedsRegenerate(StructureDefinition p) { 312 boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse"); 313 if (needs) { 314 p.setUserData("hack.regnerated", "yes"); 315 } 316 return needs; 317 } 318 319 @Override 320 public boolean isPrimitiveType(String type) { 321 StructureDefinition sd = context.fetchTypeDefinition(type); 322 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 323 } 324 325 @Override 326 public boolean isDatatype(String type) { 327 StructureDefinition sd = context.fetchTypeDefinition(type); 328 return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 329 } 330 331 @Override 332 public boolean isResource(String t) { 333 StructureDefinition sd; 334 try { 335 sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t); 336 } catch (Exception e) { 337 return false; 338 } 339 if (sd == null) 340 return false; 341 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) 342 return false; 343 return sd.getKind() == StructureDefinitionKind.RESOURCE; 344 } 345 346 @Override 347 public boolean hasLinkFor(String typeSimple) { 348 return false; 349 } 350 351 @Override 352 public String getLinkFor(String corePath, String typeSimple) { 353 return null; 354 } 355 356 @Override 357 public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) { 358 return null; 359 } 360 361 @Override 362 public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) { 363 return null; 364 } 365 366 @Override 367 public String getLinkForProfile(StructureDefinition profile, String url) { 368 return null; 369 } 370 @Override 371 public boolean prependLinks() { 372 return false; 373 } 374 375 public boolean isPrimitiveDatatype(String type) { 376 StructureDefinition sd = context.fetchTypeDefinition(type); 377 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 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 && ed != null && ed.hasExtension(ToolingExtensions.EXT_JSON_NAME) && 384 key.equals(ToolingExtensions.readStringExtension(ed, ToolingExtensions.EXT_JSON_NAME))) { 385 return sd; 386 } 387 } 388 return null; 389 } 390 391 public List<String> getConcreteResources() { 392 if (concreteResourceNames == null) { 393 concreteResourceNames = new ArrayList<>(); 394 Set<String> names = new HashSet<>(); 395 for (StructureDefinition sd : allStructures()) { 396 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract()) { 397 names.add(sd.getType()); 398 } 399 } 400 concreteResourceNames.addAll(Utilities.sorted(names)); 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} 446