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