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.setWebPath(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 Map<String, Loadable<ValueSet>> valueSets;
066  private Map<String, Loadable<CodeSystem>> codeSystems;
067  private List<Loadable<StructureDefinition>> structures;
068  private IWorkerContext context;
069  private JsonParser json;
070  
071  public R5ExtensionsLoader(BasePackageCacheManager pcm, IWorkerContext context) {
072    super();
073    this.pcm = pcm;
074    this.context = context;
075
076    valueSets = new HashMap<>();
077    codeSystems = new HashMap<>();
078    structures = new ArrayList<>();
079  }
080
081  public void load() throws FHIRException, IOException {
082    pckCore = pcm.loadPackage("hl7.fhir.r5.core", "5.0.0");
083    loadDetails(pckCore); 
084  }
085
086  private void loadDetails(NpmPackage pck) throws IOException {
087    json = new JsonParser();
088
089    String[] types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem" };
090    for (PackageResourceInformation pri : pck.listIndexedResources(types)) {
091      if (pri.getResourceType().equals("CodeSystem")) {
092        codeSystems.put(pri.getUrl(), new Loadable<CodeSystem>(pri, pck));
093        codeSystems.put(pri.getUrl()+"|"+pri.getVersion(), new Loadable<CodeSystem>(pri, pck));
094      } else if (pri.getResourceType().equals("ValueSet")) {
095        valueSets.put(pri.getUrl(), new Loadable<ValueSet>(pri, pck));
096        valueSets.put(pri.getUrl()+"|"+pri.getVersion(), new Loadable<ValueSet>(pri, pck));
097      } else if (pri.getResourceType().equals("StructureDefinition"))  {
098        structures.add(new Loadable<StructureDefinition>(pri, pck));
099      }
100    }
101  }
102  
103//  public void loadR5Extensions() throws FHIRException, IOException {
104//    count = 0;
105//    List<String> typeNames = new ContextUtilities(context).getTypeNames();
106//    for (Loadable<StructureDefinition> lsd : structures) {
107//      if (lsd.info.getStatedType().equals("Extension") && !context.hasResource(StructureDefinition.class, lsd.info.getUrl())) {
108//        StructureDefinition sd = lsd.getResource();
109//        if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
110//          if (survivesStrippingTypes(sd, context, typeNames)) {
111//            count++;
112//            sd.setWebPath(Utilities.pathURL(pckExt.getWebLocation(), "extension-"+sd.getId().toLowerCase()+".html"));
113//            registerTerminologies(sd);
114//            context.cacheResourceFromPackage(sd, new PackageInformation(lsd.source));
115//          }
116//        }
117//      }
118//    }
119//  }
120
121  public void loadR5SpecialTypes(List<String> types) throws FHIRException, IOException {
122    for (Loadable<StructureDefinition> lsd : structures) {
123      if (Utilities.existsInList(lsd.info.getId(), types)) {
124        StructureDefinition sd = lsd.getResource();
125        count++;
126        List<ElementDefinition> rl = new ArrayList<>();
127        for (ElementDefinition ed : sd.getDifferential().getElement()) {
128          if (!stripTypes(ed, sd, types)) {
129            rl.add(ed);
130          }
131        }
132        sd.getDifferential().getElement().removeAll(rl);
133        rl.clear();
134        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
135          if (!stripTypes(ed, sd, types)) {
136            rl.add(ed);
137          }
138        } 
139        sd.getSnapshot().getElement().removeAll(rl);
140        sd.setWebPath(Utilities.pathURL(lsd.source.getWebLocation(), sd.getId().toLowerCase()+".html"));
141        registerTerminologies(sd);
142        context.cacheResourceFromPackage(sd, new PackageInformation(lsd.source));
143      }
144    }    
145  }
146  
147  private boolean stripTypes(ElementDefinition ed, StructureDefinition sd, List<String> types) {
148    if (!ed.getPath().contains(".") || !ed.hasType()) {
149      return true;
150    }
151    ed.getType().removeIf(tr -> context.fetchTypeDefinition(tr.getWorkingCode()) == null);
152    if (!ed.hasType()) {
153      return false;
154    }
155    for (TypeRefComponent tr : ed.getType()) {
156      if (tr.hasTargetProfile()) {
157        tr.getTargetProfile().removeIf(n -> !context.hasResource(StructureDefinition.class, n.asStringValue()) && !n.asStringValue().equals(sd.getUrl()) && !types.contains(tail(n.asStringValue())));
158        if (!tr.hasTargetProfile()) {
159          return false;
160        }
161      }
162    }
163    return true;
164  }
165
166  private Object tail(String s) {
167    if (s == null || !s.contains("/")) {
168      return s;
169    }
170    return s.substring(s.lastIndexOf("/")+1);
171  }
172
173  private void registerTerminologies(StructureDefinition sd) throws FHIRFormatError, FileNotFoundException, IOException {
174    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
175      if (ed.hasBinding() && ed.getBinding().hasValueSet()) {
176        String vsu = ed.getBinding().getValueSet();
177        ValueSet vs = context.fetchResource(ValueSet.class, vsu);
178        if (vs == null) {
179          loadValueSet(vsu, context, valueSets, codeSystems);
180        } else if (vs.hasVersion()) {
181          ed.getBinding().setValueSet(vs.getUrl()+"|"+vs.getVersion());
182        }
183      }
184    }
185  }
186
187  private void loadValueSet(String url, IWorkerContext context, Map<String, Loadable<ValueSet>> valueSets, Map<String, Loadable<CodeSystem>> codeSystems) throws FHIRFormatError, FileNotFoundException, IOException {
188    if (valueSets.containsKey(url)) {
189      ValueSet vs = valueSets.get(url).getResource();      
190      context.cacheResourceFromPackage(vs, vs.getSourcePackage());
191      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
192        for (CanonicalType t : inc.getValueSet()) {
193          loadValueSet(t.asStringValue(), context, valueSets, codeSystems);
194        }
195        if (inc.hasSystem()) {
196          if (!inc.hasVersion()) {
197            if (codeSystems.containsKey(inc.getSystem())) {
198              CodeSystem cs = codeSystems.get(inc.getSystem()).getResource();
199              CodeSystem csAlready = context.fetchCodeSystem(inc.getSystem());
200              if (csAlready == null) {
201                context.cacheResourceFromPackage(cs, cs.getSourcePackage());
202              }
203            }
204          } else if (context.fetchResource(CodeSystem.class, inc.getSystem(), inc.getVersion()) == null && codeSystems.containsKey(inc.getSystem()+"|"+inc.getVersion())) {
205            CodeSystem cs1 = codeSystems.get(inc.getSystem()+"|"+inc.getVersion()).getResource();
206            context.cacheResourceFromPackage(cs1, cs1.getSourcePackage());
207          }
208        }
209      }
210    }
211    
212  }
213
214  private boolean survivesStrippingTypes(StructureDefinition sd, IWorkerContext context, List<String> typeNames) {
215    for (ElementDefinition ed : sd.getDifferential().getElement()) {
216      stripTypes(ed, context, typeNames);
217    }
218    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
219      if (!stripTypes(ed, context, typeNames)) {
220        return false;
221      }
222    }  
223    return true;
224  }
225
226  private boolean stripTypes(ElementDefinition ed, IWorkerContext context, List<String> typeNames) {
227    if (!ed.getPath().contains(".") || !ed.hasType()) {
228      return true;
229    }
230    ed.getType().removeIf(tr -> !typeNames.contains(tr.getWorkingCode()));
231    if (!ed.hasType()) {
232      return false;
233    }
234    for (TypeRefComponent tr : ed.getType()) {
235      if (tr.hasTargetProfile()) {
236        tr.getTargetProfile().removeIf(n -> !context.hasResource(StructureDefinition.class, n.asStringValue()));
237        if (!tr.hasTargetProfile()) {
238          return false;
239        }
240      }
241    }
242    return true;
243  }
244
245  public BasePackageCacheManager getPcm() {
246    return pcm;
247  }
248
249  public int getCount() {
250    return count;
251  }
252
253  public byte[] getMap() throws IOException {
254   return pckCore.hasFile("other", "spec.internals") ?  TextFile.streamToBytes(pckCore.load("other", "spec.internals")) : null;
255  }
256
257  public NpmPackage getPckCore() {
258    return pckCore;
259  }
260
261
262
263  
264}