001package org.hl7.fhir.r4b.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.r4b.context.IWorkerContext;
010import org.hl7.fhir.r4b.model.Constants;
011import org.hl7.fhir.r4b.model.ElementDefinition;
012import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
013import org.hl7.fhir.r4b.model.Enumerations.FHIRVersion;
014import org.hl7.fhir.r4b.model.Enumerations.PublicationStatus;
015import org.hl7.fhir.r4b.model.StructureDefinition;
016import org.hl7.fhir.r4b.model.StructureDefinition.ExtensionContextType;
017import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
018import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
019import org.hl7.fhir.r4b.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      return XVerExtensionStatus.Unknown;
072    }
073    if (path.has("elements") || path.has("types")) {
074      return XVerExtensionStatus.Valid;
075    } else {
076      return XVerExtensionStatus.Invalid;
077    }
078  }
079
080  public String getElementId(String url) {
081    return url.substring(54);
082  }
083
084  public StructureDefinition makeDefinition(String url) {
085    String verSource = url.substring(20, 23);
086    if ("5.0".equals(verSource)) {
087      verSource = "4.6"; // for now
088    }
089    String verTarget = VersionUtilities.getMajMin(context.getVersion());
090    String e = url.substring(54);
091    JsonObject root = lists.get(verSource);
092    JsonObject path = root.getAsJsonObject(e);
093    
094    StructureDefinition sd = new StructureDefinition();
095    sd.setUserData(XVER_EXT_MARKER, "true");
096    sd.setUserData("path", PackageHacker.fixPackageUrl("https://hl7.org/fhir/versions.html#extensions"));
097    sd.setUrl(url);
098    sd.setVersion(context.getVersion());
099    sd.setFhirVersion(FHIRVersion.fromCode(context.getVersion()));
100    sd.setKind(StructureDefinitionKind.COMPLEXTYPE);
101    sd.setType("Extension");
102    sd.setDerivation(TypeDerivationRule.CONSTRAINT);
103    sd.setName("Extension-"+verSource+"-"+e);
104    sd.setTitle("Extension Definition for "+e+" for Version "+verSource);
105    sd.setStatus(PublicationStatus.ACTIVE);
106    sd.setExperimental(false);
107    sd.setDate(new Date());
108    sd.setPublisher("FHIR Project");
109    sd.setPurpose("Defined so the validator can validate cross version extensions (see http://hl7.org/fhir/versions.html#extensions)");
110    sd.setAbstract(false);
111    sd.addContext().setType(ExtensionContextType.ELEMENT).setExpression(head(e));
112    sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension");
113    if (path.has("types")) {
114      sd.getDifferential().addElement().setPath("Extension.extension").setMax("0");
115      sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url));
116      ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.value[x]").setMin(1);
117      populateTypes(path, val, verSource, verTarget);
118    } else if (path.has("elements")) {
119      for (JsonElement i : path.getAsJsonArray("elements")) {
120        String s = i.getAsString();
121        sd.getDifferential().addElement().setPath("Extension.extension").setSliceName(s);
122        sd.getDifferential().addElement().setPath("Extension.extension.extension").setMax("0");
123        sd.getDifferential().addElement().setPath("Extension.extension.url").setFixed(new UriType(s));
124        ElementDefinition val = sd.getDifferential().addElement().setPath("Extension.extension.value[x]").setMin(1);
125        JsonObject elt = root.getAsJsonObject(e+"."+s);
126        if (!elt.has("types")) {
127          throw new FHIRException("Internal error - nested elements not supported yet");
128        }
129        populateTypes(elt, val, verSource, verTarget);
130      }      
131      sd.getDifferential().addElement().setPath("Extension.url").setFixed(new UriType(url));
132      sd.getDifferential().addElement().setPath("Extension.value[x]").setMax("0");
133    } else {
134      throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid");
135    }
136    if (path.has("modifier") && path.get("modifier").getAsBoolean()) {
137      ElementDefinition baseDef = new ElementDefinition("Extension");
138      sd.getDifferential().getElement().add(0, baseDef);
139      baseDef.setIsModifier(true);
140    }
141    return sd;
142  }
143
144  public void populateTypes(JsonObject path, ElementDefinition val, String verSource, String verTarget) {
145    for (JsonElement i : path.getAsJsonArray("types")) {
146      String s = i.getAsString();
147      if (s.contains("(")) {
148        String t = s.substring(0, s.indexOf("("));
149        TypeRefComponent tr = val.addType().setCode(translateDataType(verTarget, t));
150        if (hasTargets(tr.getCode()) ) {
151          s = s.substring(t.length()+1);
152          for (String p : s.substring(0, s.length()-1).split("\\|")) {
153            if ("Any".equals(p)) {
154              tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource");
155            } else if (p.contains(",")) {
156              for (String pp : p.split("\\,")) {
157                tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+pp);                              
158              }
159            } else  {
160              tr.addTargetProfile("http://hl7.org/fhir/StructureDefinition/"+p);              
161            }
162          }
163        }
164      } else {
165        val.addType().setCode(translateDataType(verTarget, s));
166      }
167    }
168  }
169
170  private boolean hasTargets(String dt) {
171    return Utilities.existsInList(dt, "canonical", "Reference", "CodeableReference");
172  }
173
174  private String translateDataType(String v, String dt) {
175    if (VersionUtilities.versionsCompatible("1.0", v) || VersionUtilities.versionsCompatible("1.4", v)) {
176      return translateToR2(dt);
177    } else if (VersionUtilities.versionsCompatible("3.0", v)) {
178      return translateToR3(dt);
179    } else {
180      return dt;
181    }
182  }
183
184  private String translateToR3(String dt) {
185    if ("canonical".equals(dt)) {
186      return "uri";
187    } else if ("url".equals(dt)) {
188      return "uri";
189    } else {
190      return dt;
191    }
192  }
193
194  private String translateToR2(String dt) {
195    if ("canonical".equals(dt)) {
196      return "uri";
197    } else if ("url".equals(dt)) {
198      return "uri";
199    } else if ("uuid".equals(dt)) {
200      return "id";
201    } else {
202      return dt;
203    }
204  }
205
206  private String head(String id) {
207    if (id.contains(".")) {
208      return id.substring(0, id.lastIndexOf("."));
209    } else {
210      return id;
211    }
212  }
213  
214  public String getVersion(String url) {
215    return url.substring(20, 23);
216  }
217
218  public boolean matchingUrl(String url) {
219    if (url == null || url.length() < 56) {
220      return false;
221    }
222    String pfx = url.substring(0, 20);
223    String v = url.substring(20, 23);
224    String sfx = url.substring(23, 54);
225    return pfx.equals("http://hl7.org/fhir/") &&
226       isVersionPattern(v) && sfx.equals("/StructureDefinition/extension-");
227  }
228
229  private boolean isVersionPattern(String v) {
230    return v.length() == 3 && Character.isDigit(v.charAt(0)) && v.charAt(1) == '.' && Character.isDigit(v.charAt(2));
231  }
232
233}