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}