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 ValueSet vsi = context.fetchResource(ValueSet.class, t.getValue()); 194 if (vsi == null) { 195 loadValueSet(t.asStringValue(), context, valueSets, codeSystems); 196 } 197 } 198 if (inc.hasSystem()) { 199 if (!inc.hasVersion()) { 200 if (codeSystems.containsKey(inc.getSystem())) { 201 CodeSystem cs = codeSystems.get(inc.getSystem()).getResource(); 202 CodeSystem csAlready = context.fetchCodeSystem(inc.getSystem()); 203 if (csAlready == null) { 204 context.cacheResourceFromPackage(cs, cs.getSourcePackage()); 205 } 206 } 207 } else if (context.fetchResource(CodeSystem.class, inc.getSystem(), inc.getVersion()) == null && codeSystems.containsKey(inc.getSystem()+"|"+inc.getVersion())) { 208 CodeSystem cs1 = codeSystems.get(inc.getSystem()+"|"+inc.getVersion()).getResource(); 209 context.cacheResourceFromPackage(cs1, cs1.getSourcePackage()); 210 } 211 } 212 } 213 } 214 215 } 216 217 private boolean survivesStrippingTypes(StructureDefinition sd, IWorkerContext context, List<String> typeNames) { 218 for (ElementDefinition ed : sd.getDifferential().getElement()) { 219 stripTypes(ed, context, typeNames); 220 } 221 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 222 if (!stripTypes(ed, context, typeNames)) { 223 return false; 224 } 225 } 226 return true; 227 } 228 229 private boolean stripTypes(ElementDefinition ed, IWorkerContext context, List<String> typeNames) { 230 if (!ed.getPath().contains(".") || !ed.hasType()) { 231 return true; 232 } 233 ed.getType().removeIf(tr -> !typeNames.contains(tr.getWorkingCode())); 234 if (!ed.hasType()) { 235 return false; 236 } 237 for (TypeRefComponent tr : ed.getType()) { 238 if (tr.hasTargetProfile()) { 239 tr.getTargetProfile().removeIf(n -> !context.hasResource(StructureDefinition.class, n.asStringValue())); 240 if (!tr.hasTargetProfile()) { 241 return false; 242 } 243 } 244 } 245 return true; 246 } 247 248 public BasePackageCacheManager getPcm() { 249 return pcm; 250 } 251 252 public int getCount() { 253 return count; 254 } 255 256 public byte[] getMap() throws IOException { 257 return pckCore.hasFile("other", "spec.internals") ? TextFile.streamToBytes(pckCore.load("other", "spec.internals")) : null; 258 } 259 260 public NpmPackage getPckCore() { 261 return pckCore; 262 } 263 264 265 266 267}