001package org.hl7.fhir.convertors.loaders.loaderR5;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.List;
006
007import org.hl7.fhir.exceptions.FHIRException;
008import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
009import org.hl7.fhir.r5.context.IContextResourceLoader;
010import org.hl7.fhir.r5.model.CanonicalResource;
011import org.hl7.fhir.r5.model.CanonicalType;
012import org.hl7.fhir.r5.model.ElementDefinition;
013import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
014import org.hl7.fhir.r5.model.OperationDefinition;
015import org.hl7.fhir.r5.model.OperationDefinition.OperationDefinitionParameterComponent;
016import org.hl7.fhir.r5.model.Resource;
017import org.hl7.fhir.r5.model.StructureDefinition;
018import org.hl7.fhir.r5.model.UriType;
019import org.hl7.fhir.r5.model.ValueSet;
020import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
021import org.hl7.fhir.r5.utils.ToolingExtensions;
022import org.hl7.fhir.r5.utils.UserDataNames;
023import org.hl7.fhir.utilities.Utilities;
024import org.hl7.fhir.utilities.VersionUtilities;
025import org.hl7.fhir.utilities.npm.NpmPackage;
026import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
027
028import com.google.gson.JsonSyntaxException;
029
030import lombok.Getter;
031import lombok.Setter;
032import lombok.experimental.Accessors;
033
034@Accessors(chain = true)
035public abstract class BaseLoaderR5 implements IContextResourceLoader {
036
037  protected final String URL_BASE = "http://hl7.org/fhir/";
038  protected final String URL_ELEMENT_DEF_NAMESPACE = "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace";
039  protected boolean patchUrls;
040  @Getter @Setter protected boolean killPrimitives;
041  @Getter protected List<String> types = new ArrayList<>();
042  protected ILoaderKnowledgeProviderR5 lkp;
043  private boolean loadProfiles = true;
044
045  public BaseLoaderR5(List<String> types, ILoaderKnowledgeProviderR5 lkp) {
046    super();
047    this.types.addAll(types);
048    this.lkp = lkp;
049  }
050
051  public String getResourcePath(Resource resource) {
052    return lkp.getResourcePath(resource);
053  }
054
055  public void setPath(Resource r) {
056    String path = lkp.getResourcePath(r);
057    if (lkp.getWebRoot() != null) { 
058      r.setUserData(UserDataNames.render_webroot, lkp.getWebRoot());
059    } else {
060      r.setUserData(UserDataNames.render_webroot, "");      
061    }
062    if (path != null) {
063      r.setWebPath(path);
064    }
065  }
066
067  public IContextResourceLoader getNewLoader(NpmPackage npm) throws JsonSyntaxException, IOException {
068    BaseLoaderR5 ret = loaderFactory(npm);
069    ret.patchUrls = patchUrls;
070    ret.killPrimitives = killPrimitives;
071    return ret;
072  }
073
074  protected BaseLoaderR5 loaderFactory(NpmPackage npm) throws JsonSyntaxException, IOException {
075    if (VersionUtilities.isR5Plus(npm.fhirVersion())) {
076      return new R5ToR5Loader(types, lkp.forNewPackage(npm));
077    } else if (VersionUtilities.isR4BVer(npm.fhirVersion())) {
078      return new R4BToR5Loader(types, lkp.forNewPackage(npm), npm.version());
079    } else if (VersionUtilities.isR4Ver(npm.fhirVersion())) {
080      return new R4ToR5Loader(types, lkp.forNewPackage(npm), npm.version());
081    } else if (VersionUtilities.isR3Ver(npm.fhirVersion())) {
082      return new R3ToR5Loader(types, lkp.forNewPackage(npm));
083    } else if (VersionUtilities.isR2Ver(npm.fhirVersion())) {
084      return new R2ToR5Loader(types, lkp.forNewPackage(npm));
085    } else if (VersionUtilities.isR2BVer(npm.fhirVersion())) {
086      return new R2016MayToR5Loader(types, lkp.forNewPackage(npm));
087    } else {
088      throw new FHIRException("Unsupported FHIR Version " + npm.fhirVersion());
089    }
090  }
091
092  public boolean isPatchUrls() {
093    return patchUrls;
094  }
095
096  public void setPatchUrls(boolean patchUrls) {
097    this.patchUrls = patchUrls;
098  }
099
100  protected abstract String versionString();
101  
102
103  @Override
104  public String patchUrl(String url, String type) {
105    if (!patchUrls || url == null) {
106      return url;
107    } else if (url.startsWith("http://hl7.org/fhir/"+type+"/")) {
108      return "http://hl7.org/fhir/"+versionString()+"/"+url.substring(20);
109    } else if ("CodeSystem".equals(type) && url.startsWith("http://hl7.org/fhir/")) {      
110      return "http://hl7.org/fhir/"+versionString()+"/"+url.substring(20);
111    } else {
112      return url;
113    }
114  }
115
116  // we don't patch everything. It's quite hard work to do that,
117  // and we only patch URLs to support version transforms
118  // so we just patch sd/od -> vs -> cs
119  protected void doPatchUrls(Resource resource) {
120    resource.setUserData(UserDataNames.loader_urls_patched, true);
121    if (resource instanceof CanonicalResource) {
122      CanonicalResource cr = (CanonicalResource) resource;
123      cr.setUrl(patchUrl(cr.getUrl(), cr.fhirType()));
124      if (cr instanceof StructureDefinition) {
125        StructureDefinition sd = (StructureDefinition) cr;
126        sd.setBaseDefinition(patchUrl(sd.getBaseDefinition(), sd.fhirType()));
127        new ProfileUtilities(null, null, null, null).setIds(sd, false);
128        sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE));
129        for (ElementDefinition ed : sd.getSnapshot().getElement())
130          patchUrl(ed);
131        for (ElementDefinition ed : sd.getDifferential().getElement())
132          patchUrl(ed);
133      }
134
135      if (cr instanceof ValueSet) {
136        ValueSet vs = (ValueSet) cr;
137        for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
138          inc.setSystem(patchUrl(inc.getSystem(), "CodeSystem"));
139        }
140        for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
141          inc.setSystem(patchUrl(inc.getSystem(), "CodeSystem"));
142        }        
143      }
144      if (cr instanceof OperationDefinition) {
145        OperationDefinition od = (OperationDefinition) cr;
146        for (OperationDefinitionParameterComponent param : od.getParameter()) {
147          patchUrls(param);
148        }        
149      }
150    }
151  }
152  
153  private void patchUrls(OperationDefinitionParameterComponent param) {
154    if (param.hasBinding()) {
155      param.getBinding().setValueSet(patchUrl(param.getBinding().getValueSet(), "ValueSet"));      
156    }
157    for (OperationDefinitionParameterComponent p : param.getPart()) {
158      patchUrls(p);
159    }
160  }
161
162  private void patchUrl(ElementDefinition ed) {
163    for (TypeRefComponent tr : ed.getType()) {
164      if (!Utilities.isAbsoluteUrl(tr.getCode())) {
165        tr.setCode(URL_BASE+versionString()+"/StructureDefinition/"+tr.getCode());
166      }
167      for (CanonicalType s : tr.getTargetProfile()) {
168        s.setValue(patchUrl(s.getValue(), "StructureDefinition"));
169      }
170      if (tr.hasExtension(ToolingExtensions.EXT_FHIR_TYPE)) {
171        String code = ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_FHIR_TYPE);
172        String url = URL_BASE+versionString()+"/StructureDefinition/"+code;
173        ToolingExtensions.setUrlExtension(tr, ToolingExtensions.EXT_FHIR_TYPE, url);
174      }
175      for (CanonicalType c : tr.getProfile()) {
176        c.setValue(patchUrl(c.getValue(), "StructureDefinition"));
177      }
178      for (CanonicalType c : tr.getTargetProfile()) {
179        c.setValue(patchUrl(c.getValue(), "StructureDefinition"));
180      }
181    }
182    if (ed.hasBinding()) {
183      ed.getBinding().setValueSet(patchUrl(ed.getBinding().getValueSet(), "ValueSet"));
184    }
185    if (ed.hasContentReference()) {
186      ed.setContentReference(patchUrl(ed.getContentReference(), "StructureDefinition"));
187    }
188  }
189
190  public IContextResourceLoader setLoadProfiles(boolean value) {
191    loadProfiles = value;
192    return this;
193  }
194  
195  public boolean wantLoad(NpmPackage pi, PackageResourceInformation pri) {
196    if (pri.getResourceType().equals("StructureDefinition")) {
197      if (loadProfiles) {
198        return true;
199      } else {
200        return pi.isCore() && Utilities.tail(pri.getUrl()).equals(pri.getStatedType());
201      }
202    } else if (pi.isCore() && "CodeSystem".equals(pri.getResourceType()) && "spdx-license".equals(pri.getId())) {
203      return false;
204    } else {
205      return true;
206    }
207  }
208}