
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}