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