001package org.hl7.fhir.r5.context; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.HashMap; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010 011import org.hl7.fhir.exceptions.DefinitionException; 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.r5.conformance.profile.BindingResolution; 014import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; 015import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 016import org.hl7.fhir.r5.model.CanonicalResource; 017import org.hl7.fhir.r5.model.CodeSystem; 018import org.hl7.fhir.r5.model.ElementDefinition; 019import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 020import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 021import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; 022import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType; 023import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent; 024import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 025import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 026import org.hl7.fhir.r5.utils.ToolingExtensions; 027import org.hl7.fhir.r5.utils.XVerExtensionManager; 028import org.hl7.fhir.r5.model.Identifier; 029import org.hl7.fhir.r5.model.NamingSystem; 030import org.hl7.fhir.r5.model.StructureDefinition; 031import org.hl7.fhir.utilities.OIDUtils; 032import org.hl7.fhir.utilities.Utilities; 033import org.hl7.fhir.utilities.i18n.I18nConstants; 034import org.hl7.fhir.utilities.validation.ValidationMessage; 035import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 036import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 037 038public class ContextUtilities implements ProfileKnowledgeProvider { 039 040 private IWorkerContext context; 041 private boolean suppressDebugMessages; 042 private boolean ignoreProfileErrors; 043 private XVerExtensionManager xverManager; 044 private Map<String, String> oidCache = new HashMap<>(); 045 046 public ContextUtilities(IWorkerContext context) { 047 super(); 048 this.context = context; 049 } 050 051 public boolean isSuppressDebugMessages() { 052 return suppressDebugMessages; 053 } 054 055 public void setSuppressDebugMessages(boolean suppressDebugMessages) { 056 this.suppressDebugMessages = suppressDebugMessages; 057 } 058 public boolean isIgnoreProfileErrors() { 059 return ignoreProfileErrors; 060 } 061 062 public void setIgnoreProfileErrors(boolean ignoreProfileErrors) { 063 this.ignoreProfileErrors = ignoreProfileErrors; 064 } 065 066 public String oid2Uri(String oid) { 067 if (oid != null && oid.startsWith("urn:oid:")) { 068 oid = oid.substring(8); 069 } 070 if (oidCache.containsKey(oid)) { 071 return oidCache.get(oid); 072 } 073 074 String uri = OIDUtils.getUriForOid(oid); 075 if (uri != null) { 076 oidCache.put(oid, uri); 077 return uri; 078 } 079 CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables"); 080 if (cs != null) { 081 for (ConceptDefinitionComponent cc : cs.getConcept()) { 082 for (ConceptPropertyComponent cp : cc.getProperty()) { 083 if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) { 084 for (ConceptPropertyComponent cp2 : cc.getProperty()) { 085 if ("v2-cs-uri".equals(cp2.getCode())) { 086 oidCache.put(oid, cp2.getValue().primitiveValue()); 087 return cp2.getValue().primitiveValue(); 088 } 089 } 090 } 091 } 092 } 093 } 094 for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) { 095 if (("urn:oid:"+oid).equals(css.getUrl())) { 096 oidCache.put(oid, css.getUrl()); 097 return css.getUrl(); 098 } 099 for (Identifier id : css.getIdentifier()) { 100 if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) { 101 oidCache.put(oid, css.getUrl()); 102 return css.getUrl(); 103 } 104 } 105 } 106 for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) { 107 if (hasOid(ns, oid)) { 108 uri = getUri(ns); 109 if (uri != null) { 110 oidCache.put(oid, null); 111 return null; 112 } 113 } 114 } 115 oidCache.put(oid, null); 116 return null; 117 } 118 119 private String getUri(NamingSystem ns) { 120 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 121 if (id.getType() == NamingSystemIdentifierType.URI) 122 return id.getValue(); 123 } 124 return null; 125 } 126 127 private boolean hasOid(NamingSystem ns, String oid) { 128 for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { 129 if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid)) 130 return true; 131 } 132 return false; 133 } 134 135 /** 136 * @return a list of the resource and type names defined for this version 137 */ 138 public List<String> getTypeNames() { 139 Set<String> result = new HashSet<String>(); 140 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 141 if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 142 result.add(sd.getName()); 143 } 144 return Utilities.sorted(result); 145 } 146 147 148 /** 149 * @return a set of the resource and type names defined for this version 150 */ 151 public Set<String> getTypeNameSet() { 152 Set<String> result = new HashSet<String>(); 153 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 154 if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) 155 result.add(sd.getName()); 156 } 157 return result; 158 } 159 160 public String getLinkForUrl(String corePath, String url) { 161 if (url == null) { 162 return null; 163 } 164 165 if (context.hasResource(CanonicalResource.class, url)) { 166 CanonicalResource cr = context.fetchResource(CanonicalResource.class, url); 167 return cr.getUserString("path"); 168 } 169 return null; 170 } 171 172 173 protected String tail(String url) { 174 if (Utilities.noString(url)) { 175 return "noname"; 176 } 177 if (url.contains("/")) { 178 return url.substring(url.lastIndexOf("/")+1); 179 } 180 return url; 181 } 182 183 private boolean hasUrlProperty(StructureDefinition sd) { 184 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 185 if (ed.getPath().equals(sd.getType()+".url")) { 186 return true; 187 } 188 } 189 return false; 190 } 191 192 // -- profile services --------------------------------------------------------- 193 194 195 /** 196 * @return a list of the resource names that are canonical resources defined for this version 197 */ 198 public List<String> getCanonicalResourceNames() { 199 List<String> names = new ArrayList<>(); 200 for (StructureDefinition sd : allStructures()) { 201 if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) { 202 names.add(sd.getType()); 203 } 204 } 205 return names; 206 } 207 208 /** 209 * @return a list of all structure definitions, with snapshots generated (if possible) 210 */ 211 public List<StructureDefinition> allStructures(){ 212 List<StructureDefinition> result = new ArrayList<StructureDefinition>(); 213 Set<StructureDefinition> set = new HashSet<StructureDefinition>(); 214 for (StructureDefinition sd : getStructures()) { 215 if (!set.contains(sd)) { 216 try { 217 generateSnapshot(sd); 218 // new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd); 219 } catch (Exception e) { 220 if (!isSuppressDebugMessages()) { 221 System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage()); 222 if (context.getLogger().isDebugLogging()) { 223 e.printStackTrace(); 224 } 225 } 226 } 227 result.add(sd); 228 set.add(sd); 229 } 230 } 231 return result; 232 } 233 234 /** 235 * @return a list of all structure definitions, without trying to generate snapshots 236 */ 237 public List<StructureDefinition> getStructures() { 238 return context.fetchResourcesByType(StructureDefinition.class); 239 } 240 241 /** 242 * Given a structure definition, generate a snapshot (or regenerate it) 243 * @param p 244 * @throws DefinitionException 245 * @throws FHIRException 246 */ 247 public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException { 248 generateSnapshot(p, false); 249 } 250 251 public void generateSnapshot(StructureDefinition p, boolean ifLogical) { 252 if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p) ) && (ifLogical || p.getKind() != StructureDefinitionKind.LOGICAL)) { 253 if (!p.hasBaseDefinition()) 254 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl())); 255 StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p); 256 if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) { 257 sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion()); 258 } 259 if (sd == null) { 260 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition())); 261 } 262 List<ValidationMessage> msgs = new ArrayList<ValidationMessage>(); 263 List<String> errors = new ArrayList<String>(); 264 ProfileUtilities pu = new ProfileUtilities(context, msgs, this); 265 pu.setAutoFixSliceNames(true); 266 pu.setThrowException(false); 267 if (xverManager == null) { 268 xverManager = new XVerExtensionManager(context); 269 } 270 pu.setXver(xverManager); 271 if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) { 272 pu.sortDifferential(sd, p, p.getUrl(), errors, true); 273 } 274 pu.setDebug(false); 275 for (String err : errors) 276 msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); 277 pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName()); 278 for (ValidationMessage msg : msgs) { 279 if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) 280 throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage())); 281 } 282 if (!p.hasSnapshot()) 283 throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl())); 284 pu = null; 285 } 286 p.setGeneratedSnapshot(true); 287 } 288 289 290 // work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind. 291 private boolean isProfileNeedsRegenerate(StructureDefinition p) { 292 boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse"); 293 if (needs) { 294 p.setUserData("hack.regnerated", "yes"); 295 } 296 return needs; 297 } 298 299 @Override 300 public boolean isPrimitiveType(String type) { 301 StructureDefinition sd = context.fetchTypeDefinition(type); 302 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 303 } 304 305 @Override 306 public boolean isDatatype(String type) { 307 StructureDefinition sd = context.fetchTypeDefinition(type); 308 return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION; 309 } 310 311 @Override 312 public boolean isResource(String t) { 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 boolean isPrimitiveDatatype(String type) { 356 StructureDefinition sd = context.fetchTypeDefinition(type); 357 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 358 } 359 360 public StructureDefinition fetchByJsonName(String key) { 361 for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) { 362 ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); 363 if (sd.getKind() == StructureDefinitionKind.LOGICAL && ed != null && ed.hasExtension(ToolingExtensions.EXT_JSON_NAME) && 364 key.equals(ToolingExtensions.readStringExtension(ed, ToolingExtensions.EXT_JSON_NAME))) { 365 return sd; 366 } 367 } 368 return null; 369 } 370 371} 372