001package org.hl7.fhir.r5.conformance;
002
003import java.io.FileNotFoundException;
004import java.io.IOException;
005import java.util.ArrayList;
006import java.util.Comparator;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010
011import org.hl7.fhir.exceptions.FHIRException;
012import org.hl7.fhir.exceptions.FHIRFormatError;
013import org.hl7.fhir.r5.conformance.R5ExtensionsLoader.Loadable;
014import org.hl7.fhir.r5.context.ContextUtilities;
015import org.hl7.fhir.r5.context.IWorkerContext;
016import org.hl7.fhir.r5.formats.JsonParser;
017import org.hl7.fhir.r5.model.CanonicalResource;
018import org.hl7.fhir.r5.model.CanonicalType;
019import org.hl7.fhir.r5.model.CodeSystem;
020import org.hl7.fhir.r5.model.ElementDefinition;
021import org.hl7.fhir.r5.model.StructureDefinition;
022import org.hl7.fhir.r5.model.ValueSet;
023import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
024import org.hl7.fhir.r5.model.PackageInformation;
025import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
026import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
027import org.hl7.fhir.r5.utils.ResourceSorters;
028import org.hl7.fhir.utilities.TextFile;
029import org.hl7.fhir.utilities.Utilities;
030import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
031import org.hl7.fhir.utilities.npm.NpmPackage;
032import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
033
034public class R5ExtensionsLoader {
035  
036  public static class CanonicalResourceSortByUrl<T extends CanonicalResource> implements Comparator<Loadable<T>> {
037
038    @Override
039    public int compare(Loadable<T> arg0, Loadable<T> arg1) {
040      return arg0.info.getUrl().compareTo(arg1.info.getUrl());
041    }
042  }
043
044  public class Loadable<T extends CanonicalResource> {
045    public Loadable(PackageResourceInformation info, NpmPackage source) {
046      this.info = info;
047      this.source = source;
048    }
049    private T resource;
050    private NpmPackage source;
051    private PackageResourceInformation info;
052    public T getResource() throws FHIRFormatError, FileNotFoundException, IOException {
053      if (resource == null) {
054        CanonicalResource r = (CanonicalResource) json.parse(source.load(info));
055        r.setUserData("path", Utilities.pathURL(source.getWebLocation(), r.fhirType().toLowerCase()+ "-"+r.getId().toLowerCase()+".html"));
056        resource = (T) r;
057      }
058      return resource;
059    }
060  }
061
062  private BasePackageCacheManager pcm;
063  private int count;
064  private NpmPackage pckCore;
065  private NpmPackage pckExt;
066  private Map<String, Loadable<ValueSet>> valueSets;
067  private Map<String, Loadable<CodeSystem>> codeSystems;
068  private List<Loadable<StructureDefinition>> structures;
069  private IWorkerContext context;
070  private JsonParser json;
071  
072  public R5ExtensionsLoader(BasePackageCacheManager pcm, IWorkerContext context) {
073    super();
074    this.pcm = pcm;
075    this.context = context;
076
077    valueSets = new HashMap<>();
078    codeSystems = new HashMap<>();
079    structures = new ArrayList<>();
080  }
081
082  public void load() throws FHIRException, IOException {
083    pckCore = pcm.loadPackage("hl7.fhir.r5.core", "current");
084    loadDetails(pckCore); 
085    pckExt = pcm.loadPackage("hl7.fhir.uv.extensions", "current");
086    loadDetails(pckExt); 
087  }
088
089  private void loadDetails(NpmPackage pck) throws IOException {
090    json = new JsonParser();
091
092    String[] types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem" };
093    for (PackageResourceInformation pri : pck.listIndexedResources(types)) {
094      if (pri.getResourceType().equals("CodeSystem")) {
095        codeSystems.put(pri.getUrl(), new Loadable<CodeSystem>(pri, pck));
096        codeSystems.put(pri.getUrl()+"|"+pri.getVersion(), new Loadable<CodeSystem>(pri, pck));
097      } else if (pri.getResourceType().equals("ValueSet")) {
098        valueSets.put(pri.getUrl(), new Loadable<ValueSet>(pri, pck));
099        valueSets.put(pri.getUrl()+"|"+pri.getVersion(), new Loadable<ValueSet>(pri, pck));
100      } else if (pri.getResourceType().equals("StructureDefinition"))  {
101        structures.add(new Loadable<StructureDefinition>(pri, pck));
102      }
103    }
104  }
105  
106  public void loadR5Extensions() throws FHIRException, IOException {
107    count = 0;
108    List<String> typeNames = new ContextUtilities(context).getTypeNames();
109    for (Loadable<StructureDefinition> lsd : structures) {
110      if (lsd.info.getStatedType().equals("Extension") && !context.hasResource(StructureDefinition.class, lsd.info.getUrl())) {
111        StructureDefinition sd = lsd.getResource();
112        if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
113          if (survivesStrippingTypes(sd, context, typeNames)) {
114            count++;
115            sd.setUserData("path", Utilities.pathURL(pckExt.getWebLocation(), "extension-"+sd.getId().toLowerCase()+".html"));
116            registerTerminologies(sd);
117            context.cacheResourceFromPackage(sd, new PackageInformation(lsd.source));
118          }
119        }
120      }
121    }
122  }
123
124  public void loadR5SpecialTypes(List<String> types) throws FHIRException, IOException {
125    for (Loadable<StructureDefinition> lsd : structures) {
126      StructureDefinition sd = lsd.getResource();
127      if (Utilities.existsInList(sd.getType(), types)) {
128        count++;
129        sd.setUserData("path", Utilities.pathURL(lsd.source.getWebLocation(), sd.getId().toLowerCase()+".html"));
130        registerTerminologies(sd);
131        context.cacheResourceFromPackage(sd, new PackageInformation(lsd.source));
132      }
133    }    
134  }
135
136  private void registerTerminologies(StructureDefinition sd) throws FHIRFormatError, FileNotFoundException, IOException {
137    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
138      if (ed.hasBinding() && ed.getBinding().hasValueSet()) {
139        String vsu = ed.getBinding().getValueSet();
140        ValueSet vs = context.fetchResource(ValueSet.class, vsu);
141        if (vs == null) {
142          loadValueSet(vsu, context, valueSets, codeSystems);
143        } else if (vs.hasVersion()) {
144          ed.getBinding().setValueSet(vs.getUrl()+"|"+vs.getVersion());
145        }
146      }
147    }
148  }
149
150  private void loadValueSet(String url, IWorkerContext context, Map<String, Loadable<ValueSet>> valueSets, Map<String, Loadable<CodeSystem>> codeSystems) throws FHIRFormatError, FileNotFoundException, IOException {
151    if (valueSets.containsKey(url)) {
152      ValueSet vs = valueSets.get(url).getResource();      
153      context.cacheResourceFromPackage(vs, vs.getSourcePackage());
154      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
155        for (CanonicalType t : inc.getValueSet()) {
156          loadValueSet(t.asStringValue(), context, valueSets, codeSystems);
157        }
158        if (inc.hasSystem() && !inc.hasVersion()) {
159          if (codeSystems.containsKey(inc.getSystem())) {
160            CodeSystem cs = codeSystems.get(inc.getSystem()).getResource();
161            inc.setVersion(cs.getVersion());
162            context.cacheResourceFromPackage(cs, cs.getSourcePackage());
163          } else if (!context.hasResource(CodeSystem.class, inc.getSystem()) && codeSystems.containsKey(inc.getSystem())) {
164            CodeSystem cs1 = codeSystems.get(inc.getSystem()).getResource();
165            context.cacheResourceFromPackage(cs1, cs1.getSourcePackage());
166          }
167        }
168      }
169    }
170    
171  }
172
173  private boolean survivesStrippingTypes(StructureDefinition sd, IWorkerContext context, List<String> typeNames) {
174    for (ElementDefinition ed : sd.getDifferential().getElement()) {
175      stripTypes(ed, context, typeNames);
176    }
177    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
178      if (!stripTypes(ed, context, typeNames)) {
179        return false;
180      }
181    }  
182    return true;
183  }
184
185  private boolean stripTypes(ElementDefinition ed, IWorkerContext context, List<String> typeNames) {
186    if (!ed.getPath().contains(".") || !ed.hasType()) {
187      return true;
188    }
189    ed.getType().removeIf(tr -> !typeNames.contains(tr.getWorkingCode()));
190    if (!ed.hasType()) {
191      return false;
192    }
193    for (TypeRefComponent tr : ed.getType()) {
194      if (tr.hasTargetProfile()) {
195        tr.getTargetProfile().removeIf(n -> !context.hasResource(StructureDefinition.class, n.asStringValue()));
196        if (!tr.hasTargetProfile()) {
197          return false;
198        }
199      }
200    }
201    return true;
202  }
203
204  public BasePackageCacheManager getPcm() {
205    return pcm;
206  }
207
208  public int getCount() {
209    return count;
210  }
211
212  public byte[] getMap() throws IOException {
213   return pckCore.hasFile("other", "spec.internals") ?  TextFile.streamToBytes(pckCore.load("other", "spec.internals")) : null;
214  }
215
216  public NpmPackage getPckCore() {
217    return pckCore;
218  }
219
220  public NpmPackage getPckExt() {
221    return pckExt;
222  }
223
224
225  
226}