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.Constants; 011import org.hl7.fhir.r5.model.ElementDefinition; 012import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 013import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; 014import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 015import org.hl7.fhir.r5.model.StructureDefinition; 016import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; 017import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 018import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; 019import org.hl7.fhir.r5.model.UriType; 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 027public class XVerExtensionManager { 028 029 public enum XVerExtensionStatus { 030 BadVersion, Unknown, Invalid, Valid 031 } 032 033 public static final String XVER_EXT_MARKER = "XVER_EXT_MARKER"; 034 035 private Map<String, JsonObject> lists = new HashMap<>(); 036 private IWorkerContext context; 037 038 public XVerExtensionManager(IWorkerContext context) { 039 this.context = context; 040 } 041 042 public boolean isR5(String url) { 043 String v = url.substring(20, 23); 044 return "5.0".equals(v); 045 } 046 047 public XVerExtensionStatus status(String url) throws FHIRException { 048 if (url.length() < 24) { 049 return XVerExtensionStatus.Invalid; 050 } 051 String v = url.substring(20, 23); 052 String e = url.substring(54); 053 if (!lists.containsKey(v)) { 054 if (context.hasBinaryKey("xver-paths-"+v+".json")) { 055 try { 056 lists.put(v, JsonParser.parseObject(context.getBinaryForKey("xver-paths-"+v+".json"))); 057 } catch (IOException e1) { 058 throw new FHIRException(e); 059 } 060 } else { 061 return XVerExtensionStatus.BadVersion; 062 } 063 } 064 JsonObject root = lists.get(v); 065 JsonObject path = root.getJsonObject(e); 066 if (path == null) { 067 path = root.getJsonObject(e+"[x]"); 068 } 069 if (path == null) { 070 return XVerExtensionStatus.Unknown; 071 } 072 if (path.has("elements") || path.has("types")) { 073 return XVerExtensionStatus.Valid; 074 } else { 075 return XVerExtensionStatus.Invalid; 076 } 077 } 078 079 public String getElementId(String url) { 080 return url.substring(54); 081 } 082 083 public StructureDefinition makeDefinition(String url) { 084 String verSource = url.substring(20, 23); 085 String verTarget = VersionUtilities.getMajMin(context.getVersion()); 086 String e = url.substring(54); 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 sd.setUserData("path", PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions")); 096 sd.setUrl(url); 097 sd.setVersion(context.getVersion()); 098 sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion())); 099 sd.setKind(StructureDefinitionKind.COMPLEXTYPE); 100 sd.setType("Extension"); 101 sd.setDerivation(TypeDerivationRule.CONSTRAINT); 102 sd.setName("Extension-"+verSource+"-"+e); 103 sd.setTitle("Extension Definition for "+e+" for Version "+verSource); 104 sd.setStatus(PublicationStatus.ACTIVE); 105 sd.setExperimental(false); 106 sd.setDate(new Date()); 107 sd.setPublisher("FHIR Project"); 108 sd.setPurpose("Defined so the validator can validate cross version extensions (see http://hl7.org/fhir/versions.html#extensions)"); 109 sd.setAbstract(false); 110 sd.addContext().setType(ExtensionContextType.ELEMENT).setExpression(head(e)); 111 sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension"); 112 if (path.has("types")) { 113 sd.getDifferential().addElement().setPath("Extension.extension").setMax("0"); 114 sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url)); 115 ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.value[x]").setMin(1); 116 populateTypes(path, val, verSource, verTarget); 117 } else if (path.has("elements")) { 118 for (JsonElement i : path.forceArray("elements").getItems()) { 119 String apath = e+"."+i.asString(); 120 JsonObject elt = root.getJsonObject(apath); 121 if (elt != null) { 122 genExtensionContents(root, apath, verSource, verTarget, sd, i, elt, "Extension.extension"); 123 } 124 } 125 sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url)); 126 sd.getDifferential().addElement().setPath("Extension.value[x]").setMax("0"); 127 } else { 128 throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid"); 129 } 130 if (path.has("modifier") && path.asBoolean("modifier")) { 131 ElementDefinition baseDef = new ElementDefinition("Extension"); 132 sd.getDifferential().getElement().add(0, baseDef); 133 baseDef.setIsModifier(true); 134 } 135 return sd; 136 } 137 138 private void genExtensionContents(JsonObject root, String apath, String verSource, String verTarget, StructureDefinition sd, JsonElement i, JsonObject elt, String epath) { 139 String s = i.asString().replace("[x]", ""); 140 sd.getDifferential().addElement().setPath(epath).setSliceName(s); 141 if (elt.has("types")) { 142 sd.getDifferential().addElement().setPath(epath+".extension").setMax("0"); 143 sd.getDifferential().addElement().setPath(epath+".url").setFixed(new UriType(s)); 144 ElementDefinition val = sd.getDifferential().addElement().setPath(epath+".value[x]").setMin(1); 145 populateTypes(elt, val, verSource, verTarget); 146 } else if (elt.has("elements")) { 147 for (JsonElement ic : elt.forceArray("elements").getItems()) { 148 String apathC = apath+"."+ic.asString(); 149 JsonObject eltC = root.getJsonObject(apathC); 150 if (eltC != null) { 151 genExtensionContents(root, apathC, verSource, verTarget, sd, ic, eltC, epath+".extension"); 152 } 153 } 154 sd.getDifferential().addElement().setPath(epath+".url").setFixed(new UriType(s)); 155 sd.getDifferential().addElement().setPath(epath+".value[x]").setMax("0"); 156 } else { 157 throw new FHIRException("Internal error - unknown element "+apath); 158 } 159 } 160 161 public void populateTypes(JsonObject path, ElementDefinition val, String verSource, String verTarget) { 162 for (JsonElement i : path.forceArray("types").getItems()) { 163 String s = i.asString(); 164 if (!s.startsWith("!")) { 165 if (s.contains("(")) { 166 String t = s.substring(0, s.indexOf("(")); 167 TypeRefComponent tr = val.addType().setCode(translateDataType(verTarget, t)); 168 if (hasTargets(tr.getCode()) ) { 169 s = s.substring(t.length()+1); 170 for (String p : s.substring(0, s.length()-1).split("\\|")) { 171 if ("Any".equals(p)) { 172 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource"); 173 } else if (p.contains(",")) { 174 for (String pp : p.split("\\,")) { 175 if (isResource(pp)) { 176 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+pp); 177 } 178 } 179 } else if (isResource(p)) { 180 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p); 181 } 182 } 183 } 184 } else { 185 val.addType().setCode(translateDataType(verTarget, s)); 186 } 187 } 188 } 189 } 190 191 // todo: translate names 192 193 private boolean isResource(String p) { 194 return context.getResourceNames().contains(p); 195 } 196 197 private boolean hasTargets(String dt) { 198 return Utilities.existsInList(dt, "canonical", "Reference", "CodeableReference"); 199 } 200 201 private String translateDataType(String v, String dt) { 202 if (VersionUtilities.versionsCompatible("1.0", v) || VersionUtilities.versionsCompatible("1.4", v)) { 203 return translateToR2(dt); 204 } else if (VersionUtilities.versionsCompatible("3.0", v)) { 205 return translateToR3(dt); 206 } else { 207 return dt; 208 } 209 } 210 211 private String translateToR3(String dt) { 212 if ("canonical".equals(dt)) { 213 return "uri"; 214 } else if ("url".equals(dt)) { 215 return "uri"; 216 } else { 217 return dt; 218 } 219 } 220 221 private String translateToR2(String dt) { 222 if ("canonical".equals(dt)) { 223 return "uri"; 224 } else if ("url".equals(dt)) { 225 return "uri"; 226 } else if ("uuid".equals(dt)) { 227 return "id"; 228 } else { 229 return dt; 230 } 231 } 232 233 private String head(String id) { 234 if (id.contains(".")) { 235 return id.substring(0, id.lastIndexOf(".")); 236 } else { 237 return id; 238 } 239 } 240 241 public String getVersion(String url) { 242 return url.substring(20, 23); 243 } 244 245 public boolean matchingUrl(String url) { 246 if (url == null || url.length() < 56) { 247 return false; 248 } 249 String pfx = url.substring(0, 20); 250 String v = url.substring(20, 23); 251 String sfx = url.substring(23, 54); 252 return pfx.equals("http://hl7.org/fhir/") && 253 isVersionPattern(v) && sfx.equals("/StructureDefinition/extension-"); 254 } 255 256 private boolean isVersionPattern(String v) { 257 return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2)); 258 } 259 260}