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}