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