
001package org.hl7.fhir.r5.utils; 002 003import java.io.IOException; 004import java.util.Date; 005import java.util.HashMap; 006import java.util.Map; 007 008import org.hl7.fhir.exceptions.FHIRException; 009import org.hl7.fhir.r5.context.IWorkerContext; 010import org.hl7.fhir.r5.model.ElementDefinition; 011import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 012import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 013import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 014import org.hl7.fhir.r5.model.StructureDefinition; 015import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 016import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 017import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 018import org.hl7.fhir.r5.model.UriType; 019import org.hl7.fhir.utilities.Utilities; 020import org.hl7.fhir.utilities.VersionUtilities; 021import org.hl7.fhir.utilities.json.model.JsonElement; 022import org.hl7.fhir.utilities.json.model.JsonObject; 023import org.hl7.fhir.utilities.json.parser.JsonParser; 024import org.hl7.fhir.utilities.npm.PackageHacker; 025 026public class XVerExtensionManager { 027 028 public enum XVerExtensionStatus { 029 BadVersion, Unknown, Invalid, Valid 030 } 031 032 public static final String XVER_EXT_MARKER = "XVER_EXT_MARKER"; 033 034 private Map<String, JsonObject> lists = new HashMap<>(); 035 private IWorkerContext context; 036 037 public XVerExtensionManager(IWorkerContext context) { 038 this.context = context; 039 } 040 041 public boolean isR5(String url) { 042 String v = url.substring(20, 23); 043 return "5.0".equals(v); 044 } 045 046 public XVerExtensionStatus status(String url) throws FHIRException { 047 if (url.length() < 24) { 048 return XVerExtensionStatus.Invalid; 049 } 050 String v = url.substring(20, 23); 051 String e = url.substring(54); 052 if (!lists.containsKey(v)) { 053 if (context.hasBinaryKey("xver-paths-"+v+".json")) { 054 try { 055 lists.put(v, JsonParser.parseObject(context.getBinaryForKey("xver-paths-"+v+".json"))); 056 } catch (IOException e1) { 057 throw new FHIRException(e); 058 } 059 } else { 060 return XVerExtensionStatus.BadVersion; 061 } 062 } 063 JsonObject root = lists.get(v); 064 JsonObject path = root.getJsonObject(e); 065 if (path == null) { 066 path = root.getJsonObject(e+"[x]"); 067 } 068 if (path == null) { 069 return XVerExtensionStatus.Unknown; 070 } 071 if (path.has("elements") || path.has("types")) { 072 return XVerExtensionStatus.Valid; 073 } else { 074 return XVerExtensionStatus.Invalid; 075 } 076 } 077 078 public String getElementId(String url) { 079 return url.substring(54); 080 } 081 082 public StructureDefinition makeDefinition(String url) { 083 String verSource = url.substring(20, 23); 084 String verTarget = VersionUtilities.getMajMin(context.getVersion()); 085 String e = url.substring(54); 086 String r = e.contains(".") ? e.substring(0, e.indexOf(".")) : e; 087 JsonObject root = lists.get(verSource); 088 JsonObject path = root.getJsonObject(e); 089 if (path == null) { 090 path = root.getJsonObject(e+"[x]"); 091 } 092 093 StructureDefinition sd = new StructureDefinition(); 094 sd.setUserData(XVER_EXT_MARKER, "true"); 095 if (context.getResourceNamesAsSet().contains(r)) { 096 sd.setWebPath(Utilities.pathURL(context.getSpecUrl(), r.toLowerCase()+"-definitions.html#"+e)); 097 } else { 098 sd.setWebPath(PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions")); 099 } 100 sd.setUrl(url); 101 sd.setVersion(context.getVersion()); 102 sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion())); 103 sd.setKind(StructureDefinitionKind.COMPLEXTYPE); 104 sd.setType("Extension"); 105 sd.setDerivation(TypeDerivationRule.CONSTRAINT); 106 sd.setName("Extension-"+verSource+"-"+e); 107 sd.setTitle("Extension Definition for "+e+" for Version "+verSource); 108 sd.setStatus(PublicationStatus.ACTIVE); 109 sd.setExperimental(false); 110 sd.setDate(new Date()); 111 sd.setPublisher("FHIR Project"); 112 sd.setPurpose("Defined so the validator can validate cross version extensions (see http://hl7.org/fhir/versions.html#extensions)"); 113 sd.setAbstract(false); 114 sd.addContext().setType(ExtensionContextType.ELEMENT).setExpression(head(e)); 115 sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension"); 116 if (path.has("types")) { 117 sd.getDifferential().addElement().setPath("Extension.extension").setMax("0"); 118 sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url)); 119 ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.value[x]").setMin(1); 120 populateTypes(path, val, verSource, verTarget); 121 } else if (path.has("elements")) { 122 for (JsonElement i : path.forceArray("elements").getItems()) { 123 String apath = e+"."+i.asString(); 124 JsonObject elt = root.getJsonObject(apath); 125 if (elt != null) { 126 genExtensionContents(root, apath, verSource, verTarget, sd, i, elt, "Extension.extension"); 127 } 128 } 129 sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url)); 130 sd.getDifferential().addElement().setPath("Extension.value[x]").setMax("0"); 131 } else { 132 throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid"); 133 } 134 if (path.has("modifier") && path.asBoolean("modifier")) { 135 ElementDefinition baseDef = new ElementDefinition("Extension"); 136 sd.getDifferential().getElement().add(0, baseDef); 137 baseDef.setIsModifier(true); 138 } 139 return sd; 140 } 141 142 private void genExtensionContents(JsonObject root, String apath, String verSource, String verTarget, StructureDefinition sd, JsonElement i, JsonObject elt, String epath) { 143 String s = i.asString().replace("[x]", ""); 144 sd.getDifferential().addElement().setPath(epath).setSliceName(s); 145 if (elt.has("types")) { 146 sd.getDifferential().addElement().setPath(epath+".extension").setMax("0"); 147 sd.getDifferential().addElement().setPath(epath+".url").setFixed(new UriType(s)); 148 ElementDefinition val = sd.getDifferential().addElement().setPath(epath+".value[x]").setMin(1); 149 populateTypes(elt, val, verSource, verTarget); 150 } else if (elt.has("elements")) { 151 for (JsonElement ic : elt.forceArray("elements").getItems()) { 152 String apathC = apath+"."+ic.asString(); 153 JsonObject eltC = root.getJsonObject(apathC); 154 if (eltC != null) { 155 genExtensionContents(root, apathC, verSource, verTarget, sd, ic, eltC, epath+".extension"); 156 } 157 } 158 sd.getDifferential().addElement().setPath(epath+".url").setFixed(new UriType(s)); 159 sd.getDifferential().addElement().setPath(epath+".value[x]").setMax("0"); 160 } else { 161 throw new FHIRException("Internal error - unknown element "+apath); 162 } 163 } 164 165 public void populateTypes(JsonObject path, ElementDefinition val, String verSource, String verTarget) { 166 for (JsonElement i : path.forceArray("types").getItems()) { 167 String s = i.asString(); 168 if (!s.startsWith("!")) { 169 if (s.contains("(")) { 170 String t = s.substring(0, s.indexOf("(")); 171 TypeRefComponent tr = val.addType().setCode(translateDataType(verTarget, t)); 172 if (hasTargets(tr.getCode()) ) { 173 s = s.substring(t.length()+1); 174 for (String p : s.substring(0, s.length()-1).split("\\|")) { 175 if ("Any".equals(p)) { 176 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource"); 177 } else if (p.contains(",")) { 178 for (String pp : p.split("\\,")) { 179 if (isResource(pp)) { 180 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+pp); 181 } 182 } 183 } else if (isResource(p)) { 184 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p); 185 } 186 } 187 } 188 } else { 189 val.addType().setCode(translateDataType(verTarget, s)); 190 } 191 } 192 } 193 } 194 195 // todo: translate names 196 197 private boolean isResource(String p) { 198 return context.getResourceNames().contains(p); 199 } 200 201 private boolean hasTargets(String dt) { 202 return Utilities.existsInList(dt, "canonical", "Reference", "CodeableReference"); 203 } 204 205 private String translateDataType(String v, String dt) { 206 if (VersionUtilities.versionsCompatible("1.0", v) || VersionUtilities.versionsCompatible("1.4", v)) { 207 return translateToR2(dt); 208 } else if (VersionUtilities.versionsCompatible("3.0", v)) { 209 return translateToR3(dt); 210 } else { 211 return dt; 212 } 213 } 214 215 private String translateToR3(String dt) { 216 if ("canonical".equals(dt)) { 217 return "uri"; 218 } else if ("url".equals(dt)) { 219 return "uri"; 220 } else { 221 return dt; 222 } 223 } 224 225 private String translateToR2(String dt) { 226 if ("canonical".equals(dt)) { 227 return "uri"; 228 } else if ("url".equals(dt)) { 229 return "uri"; 230 } else if ("uuid".equals(dt)) { 231 return "id"; 232 } else { 233 return dt; 234 } 235 } 236 237 private String head(String id) { 238 if (id.contains(".")) { 239 return id.substring(0, id.lastIndexOf(".")); 240 } else { 241 return id; 242 } 243 } 244 245 public String getVersion(String url) { 246 return url.substring(20, 23); 247 } 248 249 public boolean matchingUrl(String url) { 250 if (url == null || url.length() < 56) { 251 return false; 252 } 253 String pfx = url.substring(0, 20); 254 String v = url.substring(20, 23); 255 String sfx = url.substring(23, 54); 256 return pfx.equals("http://hl7.org/fhir/") && 257 isVersionPattern(v) && sfx.equals("/StructureDefinition/extension-"); 258 } 259 260 private boolean isVersionPattern(String v) { 261 return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2)); 262 } 263 264 public String getReference(String url) { 265 String version = getVersion(url); 266 String base = VersionUtilities.getSpecUrl(version); 267 if (base == null) { 268 return null; 269 } else { 270 String path = url.substring(url.indexOf("-")+1); 271 if (!path.contains(".")) { 272 return null; 273 } 274 String type = path.substring(0, path.indexOf(".")); 275 if (Utilities.existsInList(type, "Annotation", "Attachment", "Identifier", "CodeableConcept", "Coding", "Quantity", "Duration", "Range", "Period", "Ratio", "RatioRange", "SampledData", "Signature", "HumanName", "Address", "ContactPoint", "Timing")) { 276 return Utilities.pathURL(base, "datatypes-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); 277 } 278 if (Utilities.existsInList(type, "Element", "BackboneElement", "BackboneType", "PrimitiveType", "DataType", "Base")) { 279 return Utilities.pathURL(base, "types-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); 280 } 281 if (Utilities.existsInList(type, "UsageContext", "RelatedArtifact", "DataRequirement", "ParameterDefinition", "TriggerDefinition", "Expression", "ContactDetail", "ExtendedContactDetail", "VirtualServiceDetail", "Availability", "MonetaryComponent", "Contributor")) { 282 return Utilities.pathURL(base, "metadatatypes-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); 283 } 284 if (Utilities.existsInList(type, "Reference", "CodeableReference")) { 285 return Utilities.pathURL(base, "references-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); 286 } 287 if (Utilities.existsInList(type, "Meta")) { 288 return Utilities.pathURL(base, "resource-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); 289 } 290 return Utilities.pathURL(base, type.toLowerCase()+"-definitions.html#"+path+"|"+VersionUtilities.getNameForVersion(version)+" "+path); 291 } 292 } 293 294}