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.JsonTrackingParser; 023import org.hl7.fhir.utilities.npm.PackageHacker; 024 025import com.google.gson.JsonElement; 026import com.google.gson.JsonObject; 027 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 private Map<String, JsonObject> lists = new HashMap<>(); 037 private IWorkerContext context; 038 039 public XVerExtensionManager(IWorkerContext context) { 040 this.context = context; 041 } 042 043 public boolean isR5(String url) { 044 String v = url.substring(20, 23); 045 return "5.0".equals(v); 046 } 047 048 public XVerExtensionStatus status(String url) throws FHIRException { 049 if (url.length() < 24) { 050 return XVerExtensionStatus.Invalid; 051 } 052 String v = url.substring(20, 23); 053 if ("5.0".equals(v)) { 054 v = "4.6"; // for now 055 } 056 String e = url.substring(54); 057 if (!lists.containsKey(v)) { 058 if (context.getBinaries().containsKey("xver-paths-"+v+".json")) { 059 try { 060 lists.put(v, JsonTrackingParser.parseJson(context.getBinaries().get("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.getAsJsonObject(e); 070 if (path == null) { 071 path = root.getAsJsonObject(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 String verSource = url.substring(20, 23); 089 if ("5.0".equals(verSource)) { 090 verSource = "4.6"; // for now 091 } 092 String verTarget = VersionUtilities.getMajMin(context.getVersion()); 093 String e = url.substring(54); 094 JsonObject root = lists.get(verSource); 095 JsonObject path = root.getAsJsonObject(e); 096 if (path == null) { 097 path = root.getAsJsonObject(e+"[x]"); 098 } 099 100 StructureDefinition sd = new StructureDefinition(); 101 sd.setUserData(XVER_EXT_MARKER, "true"); 102 sd.setUserData("path", PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions")); 103 sd.setUrl(url); 104 sd.setVersion(context.getVersion()); 105 sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion())); 106 sd.setKind(StructureDefinitionKind.COMPLEXTYPE); 107 sd.setType("Extension"); 108 sd.setDerivation(TypeDerivationRule.CONSTRAINT); 109 sd.setName("Extension-"+verSource+"-"+e); 110 sd.setTitle("Extension Definition for "+e+" for Version "+verSource); 111 sd.setStatus(PublicationStatus.ACTIVE); 112 sd.setExperimental(false); 113 sd.setDate(new Date()); 114 sd.setPublisher("FHIR Project"); 115 sd.setPurpose("Defined so the validator can validate cross version extensions (see http://hl7.org/fhir/versions.html#extensions)"); 116 sd.setAbstract(false); 117 sd.addContext().setType(ExtensionContextType.ELEMENT).setExpression(head(e)); 118 sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension"); 119 if (path.has("types")) { 120 sd.getDifferential().addElement().setPath("Extension.extension").setMax("0"); 121 sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url)); 122 ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.value[x]").setMin(1); 123 populateTypes(path, val, verSource, verTarget); 124 } else if (path.has("elements")) { 125 for (JsonElement i : path.getAsJsonArray("elements")) { 126 String s = i.getAsString(); 127 sd.getDifferential().addElement().setPath("Extension.extension").setSliceName(s); 128 sd.getDifferential().addElement().setPath("Extension.extension.extension").setMax("0"); 129 sd.getDifferential().addElement().setPath("Extension.extension.url").setFixed(new UriType(s)); 130 ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.extension.value[x]").setMin(1); 131 JsonObject elt = root.getAsJsonObject(e+"."+s); 132 if (!elt.has("types")) { 133 throw new FHIRException("Internal error - nested elements not supported yet"); 134 } 135 populateTypes(elt, val, verSource, verTarget); 136 } 137 sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url)); 138 sd.getDifferential().addElement().setPath("Extension.value[x]").setMax("0"); 139 } else { 140 throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid"); 141 } 142 if (path.has("modifier") && path.get("modifier").getAsBoolean()) { 143 ElementDefinition baseDef = new ElementDefinition("Extension"); 144 sd.getDifferential().getElement().add(0, baseDef); 145 baseDef.setIsModifier(true); 146 } 147 return sd; 148 } 149 150 public void populateTypes(JsonObject path, ElementDefinition val, String verSource, String verTarget) { 151 for (JsonElement i : path.getAsJsonArray("types")) { 152 String s = i.getAsString(); 153 if (s.contains("(")) { 154 String t = s.substring(0, s.indexOf("(")); 155 TypeRefComponent tr = val.addType().setCode(translateDataType(verTarget, t)); 156 if (hasTargets(tr.getCode()) ) { 157 s = s.substring(t.length()+1); 158 for (String p : s.substring(0, s.length()-1).split("\\|")) { 159 if ("Any".equals(p)) { 160 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource"); 161 } else if (p.contains(",")) { 162 for (String pp : p.split("\\,")) { 163 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+pp); 164 } 165 } else { 166 tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p); 167 } 168 } 169 } 170 } else { 171 val.addType().setCode(translateDataType(verTarget, s)); 172 } 173 } 174 } 175 176 private boolean hasTargets(String dt) { 177 return Utilities.existsInList(dt, "canonical", "Reference", "CodeableReference"); 178 } 179 180 private String translateDataType(String v, String dt) { 181 if (VersionUtilities.versionsCompatible("1.0", v) || VersionUtilities.versionsCompatible("1.4", v)) { 182 return translateToR2(dt); 183 } else if (VersionUtilities.versionsCompatible("3.0", v)) { 184 return translateToR3(dt); 185 } else { 186 return dt; 187 } 188 } 189 190 private String translateToR3(String dt) { 191 if ("canonical".equals(dt)) { 192 return "uri"; 193 } else if ("url".equals(dt)) { 194 return "uri"; 195 } else { 196 return dt; 197 } 198 } 199 200 private String translateToR2(String dt) { 201 if ("canonical".equals(dt)) { 202 return "uri"; 203 } else if ("url".equals(dt)) { 204 return "uri"; 205 } else if ("uuid".equals(dt)) { 206 return "id"; 207 } else { 208 return dt; 209 } 210 } 211 212 private String head(String id) { 213 if (id.contains(".")) { 214 return id.substring(0, id.lastIndexOf(".")); 215 } else { 216 return id; 217 } 218 } 219 220 public String getVersion(String url) { 221 return url.substring(20, 23); 222 } 223 224 public boolean matchingUrl(String url) { 225 if (url == null || url.length() < 56) { 226 return false; 227 } 228 String pfx = url.substring(0, 20); 229 String v = url.substring(20, 23); 230 String sfx = url.substring(23, 54); 231 return pfx.equals("http://hl7.org/fhir/") && 232 isVersionPattern(v) && sfx.equals("/StructureDefinition/extension-"); 233 } 234 235 private boolean isVersionPattern(String v) { 236 return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2)); 237 } 238 239}