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.FileUtilities;
029import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
030import org.hl7.fhir.utilities.Utilities;
031import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
032import org.hl7.fhir.utilities.npm.NpmPackage;
033import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
034
035@MarkedToMoveToAdjunctPackage
036public class R5ExtensionsLoader {
037  
038  public static class CanonicalResourceSortByUrl<T extends CanonicalResource> implements Comparator<Loadable<T>> {
039
040    @Override
041    public int compare(Loadable<T> arg0, Loadable<T> arg1) {
042      return arg0.info.getUrl().compareTo(arg1.info.getUrl());
043    }
044  }
045
046  public class Loadable<T extends CanonicalResource> {
047    public Loadable(PackageResourceInformation info, NpmPackage source) {
048      this.info = info;
049      this.source = source;
050    }
051    private T resource;
052    private NpmPackage source;
053    private PackageResourceInformation info;
054    public T getResource() throws FHIRFormatError, FileNotFoundException, IOException {
055      if (resource == null) {
056        CanonicalResource r = (CanonicalResource) json.parse(source.load(info));
057        r.setWebPath(Utilities.pathURL(source.getWebLocation(), r.fhirType().toLowerCase()+ "-"+r.getId().toLowerCase()+".html"));
058        resource = (T) r;
059      }
060      return resource;
061    }
062  }
063
064  private BasePackageCacheManager pcm;
065  private int count;
066  private NpmPackage pckCore;
067  private Map<String, Loadable<ValueSet>> valueSets;
068  private Map<String, Loadable<CodeSystem>> codeSystems;
069  private List<Loadable<StructureDefinition>> structures;
070  private IWorkerContext context;
071  private JsonParser json;
072  
073  public R5ExtensionsLoader(BasePackageCacheManager pcm, IWorkerContext context) {
074    super();
075    this.pcm = pcm;
076    this.context = context;
077
078    valueSets = new HashMap<>();
079    codeSystems = new HashMap<>();
080    structures = new ArrayList<>();
081  }
082
083  public void load() throws FHIRException, IOException {
084    pckCore = pcm.loadPackage("hl7.fhir.r5.core", "5.0.0");
085    loadDetails(pckCore); 
086  }
087
088  private void loadDetails(NpmPackage pck) throws IOException {
089    json = new JsonParser();
090
091    String[] types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem" };
092    for (PackageResourceInformation pri : pck.listIndexedResources(types)) {
093      if (pri.getResourceType().equals("CodeSystem")) {
094        codeSystems.put(pri.getUrl(), new Loadable<CodeSystem>(pri, pck));
095        codeSystems.put(pri.getUrl()+"|"+pri.getVersion(), new Loadable<CodeSystem>(pri, pck));
096      } else if (pri.getResourceType().equals("ValueSet")) {
097        valueSets.put(pri.getUrl(), new Loadable<ValueSet>(pri, pck));
098        valueSets.put(pri.getUrl()+"|"+pri.getVersion(), new Loadable<ValueSet>(pri, pck));
099      } else if (pri.getResourceType().equals("StructureDefinition"))  {
100        structures.add(new Loadable<StructureDefinition>(pri, pck));
101      }
102    }
103  }
104  
105//  public void loadR5Extensions() throws FHIRException, IOException {
106//    count = 0;
107//    List<String> typeNames = new ContextUtilities(context).getTypeNames();
108//    for (Loadable<StructureDefinition> lsd : structures) {
109//      if (lsd.info.getStatedType().equals("Extension") && !context.hasResource(StructureDefinition.class, lsd.info.getUrl())) {
110//        StructureDefinition sd = lsd.getResource();
111//        if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
112//          if (survivesStrippingTypes(sd, context, typeNames)) {
113//            count++;
114//            sd.setWebPath(Utilities.pathURL(pckExt.getWebLocation(), "extension-"+sd.getId().toLowerCase()+".html"));
115//            registerTerminologies(sd);
116//            context.cacheResourceFromPackage(sd, new PackageInformation(lsd.source));
117//          }
118//        }
119//      }
120//    }
121//  }
122
123  public void loadR5SpecialTypes(List<String> types) throws FHIRException, IOException {
124    for (Loadable<StructureDefinition> lsd : structures) {
125      if (Utilities.existsInList(lsd.info.getId(), types)) {
126        StructureDefinition sd = lsd.getResource();
127        count++;
128        List<ElementDefinition> rl = new ArrayList<>();
129        for (ElementDefinition ed : sd.getDifferential().getElement()) {
130          if (!stripTypes(ed, sd, types)) {
131            rl.add(ed);
132          }
133        }
134        sd.getDifferential().getElement().removeAll(rl);
135        rl.clear();
136        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
137          if (!stripTypes(ed, sd, types)) {
138            rl.add(ed);
139          }
140        } 
141        sd.getSnapshot().getElement().removeAll(rl);
142        sd.setWebPath(Utilities.pathURL(lsd.source.getWebLocation(), sd.getId().toLowerCase()+".html"));
143        registerTerminologies(sd);
144        context.cacheResourceFromPackage(sd, new PackageInformation(lsd.source));
145      }
146    }    
147  }
148  
149  private boolean stripTypes(ElementDefinition ed, StructureDefinition sd, List<String> types) {
150    if (!ed.getPath().contains(".") || !ed.hasType()) {
151      return true;
152    }
153    ed.getType().removeIf(tr -> context.fetchTypeDefinition(tr.getWorkingCode()) == null);
154    if (!ed.hasType()) {
155      return false;
156    }
157    for (TypeRefComponent tr : ed.getType()) {
158      if (tr.hasTargetProfile()) {
159        tr.getTargetProfile().removeIf(n -> !context.hasResource(StructureDefinition.class, n.asStringValue()) && !n.asStringValue().equals(sd.getUrl()) && !types.contains(tail(n.asStringValue())));
160        if (!tr.hasTargetProfile()) {
161          return false;
162        }
163      }
164    }
165    return true;
166  }
167
168  private Object tail(String s) {
169    if (s == null || !s.contains("/")) {
170      return s;
171    }
172    return s.substring(s.lastIndexOf("/")+1);
173  }
174
175  private void registerTerminologies(StructureDefinition sd) throws FHIRFormatError, FileNotFoundException, IOException {
176    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
177      if (ed.hasBinding() && ed.getBinding().hasValueSet()) {
178        String vsu = ed.getBinding().getValueSet();
179        ValueSet vs = valueSets.containsKey(vsu) ? valueSets.get(vsu).getResource() : null;
180        if (vs != null) {
181          vsu = makeR5Url(vsu);
182          ed.getBinding().setValueSet(vsu);
183          if (!context.hasResource(ValueSet.class, vsu)) {
184            vs.setUrl(removeVersion(vsu));
185            loadValueSet(vs, context, valueSets, codeSystems);
186          }
187        }
188      }
189    }
190  }
191
192  private String removeVersion(String url) {
193    if (url.contains("|")) {
194      url = url.substring(0, url.indexOf("|"));
195    }
196    return url;
197  }
198
199  private String makeR5Url(String url) {
200    return url.replace("/fhir/", "/fhir/5.0/");
201  }
202
203  private void loadValueSet(ValueSet vs, IWorkerContext context, Map<String, Loadable<ValueSet>> valueSets, Map<String, Loadable<CodeSystem>> codeSystems) throws FHIRFormatError, FileNotFoundException, IOException {
204    context.cacheResourceFromPackage(vs, vs.getSourcePackage());
205    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
206      for (CanonicalType t : inc.getValueSet()) {
207        String vsu = t.getValue();
208        ValueSet vsi = valueSets.containsKey(vsu) ? valueSets.get(vsu).getResource() : null;
209        if (vsi != null) {
210          vsu = makeR5Url(vsu);
211          t.setValue(vsu);
212          vsi.setUrl(vsu);
213          if (!context.hasResource(ValueSet.class, vsu)) {
214            loadValueSet(vsi, context, valueSets, codeSystems);
215          }
216        }
217      }
218      if (inc.hasSystem()) {
219        CodeSystem cs;
220        if (inc.hasVersion()) {
221          cs = codeSystems.containsKey(inc.getSystem()+"|"+inc.getVersion()) ? codeSystems.get(inc.getSystem()+"|"+inc.getVersion()).getResource() : null;
222        } else {
223          cs = codeSystems.containsKey(inc.getSystem()) ? codeSystems.get(inc.getSystem()).getResource() : null;
224        }
225        if (cs != null) {
226          String csu = makeR5Url(inc.getSystem());
227          cs.setUrl(csu);
228          inc.setSystem(csu);
229          if (!context.hasResource(CodeSystem.class, csu)) {
230            context.cacheResourceFromPackage(cs, cs.getSourcePackage());
231          }
232        }
233      }
234    }
235  }
236
237  private boolean survivesStrippingTypes(StructureDefinition sd, IWorkerContext context, List<String> typeNames) {
238    for (ElementDefinition ed : sd.getDifferential().getElement()) {
239      stripTypes(ed, context, typeNames);
240    }
241    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
242      if (!stripTypes(ed, context, typeNames)) {
243        return false;
244      }
245    }  
246    return true;
247  }
248
249  private boolean stripTypes(ElementDefinition ed, IWorkerContext context, List<String> typeNames) {
250    if (!ed.getPath().contains(".") || !ed.hasType()) {
251      return true;
252    }
253    ed.getType().removeIf(tr -> !typeNames.contains(tr.getWorkingCode()));
254    if (!ed.hasType()) {
255      return false;
256    }
257    for (TypeRefComponent tr : ed.getType()) {
258      if (tr.hasTargetProfile()) {
259        tr.getTargetProfile().removeIf(n -> !context.hasResource(StructureDefinition.class, n.asStringValue()));
260        if (!tr.hasTargetProfile()) {
261          return false;
262        }
263      }
264    }
265    return true;
266  }
267
268  public BasePackageCacheManager getPcm() {
269    return pcm;
270  }
271
272  public int getCount() {
273    return count;
274  }
275
276  public byte[] getMap() throws IOException {
277   return pckCore.hasFile("other", "spec.internals") ?  FileUtilities.streamToBytes(pckCore.load("other", "spec.internals")) : null;
278  }
279
280  public NpmPackage getPckCore() {
281    return pckCore;
282  }
283
284
285
286  
287}