001package org.hl7.fhir.validation;
002
003import com.google.gson.JsonObject;
004import lombok.Getter;
005import org.hl7.fhir.convertors.conv10_50.VersionConvertor_10_50;
006import org.hl7.fhir.convertors.conv14_50.VersionConvertor_14_50;
007import org.hl7.fhir.convertors.conv30_50.VersionConvertor_30_50;
008import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
009import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
010import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
011import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.r5.context.SimpleWorkerContext;
014import org.hl7.fhir.r5.elementmodel.Manager;
015import org.hl7.fhir.r5.formats.JsonParser;
016import org.hl7.fhir.r5.formats.XmlParser;
017import org.hl7.fhir.r5.model.Constants;
018import org.hl7.fhir.r5.model.ImplementationGuide;
019import org.hl7.fhir.r5.model.Resource;
020import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
021import org.hl7.fhir.utilities.SimpleHTTPClient;
022import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult;
023import org.hl7.fhir.utilities.IniFile;
024import org.hl7.fhir.utilities.TextFile;
025import org.hl7.fhir.utilities.Utilities;
026import org.hl7.fhir.utilities.VersionUtilities;
027import org.hl7.fhir.utilities.json.JSONUtil;
028import org.hl7.fhir.utilities.json.JsonTrackingParser;
029import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
030import org.hl7.fhir.utilities.npm.NpmPackage;
031import org.hl7.fhir.utilities.turtle.Turtle;
032import org.hl7.fhir.utilities.xml.XMLUtil;
033import org.hl7.fhir.validation.cli.utils.Common;
034import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
035import org.w3c.dom.Document;
036
037import java.io.*;
038import java.net.URL;
039import java.util.ArrayList;
040import java.util.HashMap;
041import java.util.List;
042import java.util.Map;
043import java.util.zip.ZipEntry;
044import java.util.zip.ZipInputStream;
045
046public class IgLoader {
047
048  private static final String[] IGNORED_EXTENSIONS = {"md", "css", "js", "png", "gif", "jpg", "html", "tgz", "pack", "zip"};
049  private static final String[] EXEMPT_FILES = {"spec.internals", "version.info", "schematron.zip", "package.json"};
050
051  @Getter private final FilesystemPackageCacheManager packageCacheManager;
052  @Getter private final SimpleWorkerContext context;
053  @Getter private final String version;
054  @Getter private final boolean isDebug;
055
056  public IgLoader(FilesystemPackageCacheManager packageCacheManager,
057                  SimpleWorkerContext context,
058                  String theVersion) {
059      this(packageCacheManager, context, theVersion, false);
060  }
061
062  public IgLoader(FilesystemPackageCacheManager packageCacheManager,
063                  SimpleWorkerContext context,
064                  String theVersion,
065                  boolean isDebug) {
066      this.packageCacheManager = packageCacheManager;
067      this.context = context;
068      this.version = theVersion;
069      this.isDebug = isDebug;
070  }
071
072  public void loadIg(List<ImplementationGuide> igs,
073                     Map<String, byte[]> binaries,
074                     String src,
075                     boolean recursive) throws IOException, FHIRException {
076    NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX_OPT) && !new File(src).exists() ? getPackageCacheManager().loadPackage(src, null) : null;
077    if (npm != null) {
078      for (String s : npm.dependencies()) {
079        if (!getContext().getLoadedPackages().contains(s)) {
080          if (!VersionUtilities.isCorePackage(s)) {
081            loadIg(igs, binaries, s, false);
082          }
083        }
084      }
085      System.out.print("  Load " + src);
086      if (!src.contains("#")) {
087        System.out.print("#" + npm.version());
088      }
089      int count = getContext().loadFromPackage(npm, ValidatorUtils.loaderForVersion(npm.fhirVersion()));
090      System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")");
091    } else {
092      System.out.print("  Load " + src);
093      String canonical = null;
094      int count = 0;
095      Map<String, byte[]> source = loadIgSource(src, recursive, true);
096      String version = Constants.VERSION;
097      if (getVersion() != null) {
098        version = getVersion();
099      }
100      if (source.containsKey("version.info")) {
101        version = readInfoVersion(source.get("version.info"));
102      }
103      for (Map.Entry<String, byte[]> t : source.entrySet()) {
104        String fn = t.getKey();
105        if (!exemptFile(fn)) {
106          Resource r = loadFileWithErrorChecking(version, t, fn);
107          if (r != null) {
108            count++;
109            getContext().cacheResource(r);
110            if (r instanceof ImplementationGuide) {
111              canonical = ((ImplementationGuide) r).getUrl();
112              igs.add((ImplementationGuide) r);
113              if (canonical.contains("/ImplementationGuide/")) {
114                Resource r2 = r.copy();
115                ((ImplementationGuide) r2).setUrl(canonical.substring(0, canonical.indexOf("/ImplementationGuide/")));
116                getContext().cacheResource(r2);
117              }
118            }
119          }
120        }
121      }
122      if (canonical != null) {
123        ValidatorUtils.grabNatives(binaries, source, canonical);
124      }
125      System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")");
126    }
127  }
128
129  public Content loadContent(String source, String opName, boolean asIg) throws FHIRException, IOException {
130    Map<String, byte[]> s = loadIgSource(source, false, asIg);
131    Content res = new Content();
132    if (s.size() != 1)
133      throw new FHIRException("Unable to find resource " + source + " to " + opName);
134    for (Map.Entry<String, byte[]> t : s.entrySet()) {
135      res.focus = t.getValue();
136      if (t.getKey().endsWith(".json"))
137        res.cntType = Manager.FhirFormat.JSON;
138      else if (t.getKey().endsWith(".xml"))
139        res.cntType = Manager.FhirFormat.XML;
140      else if (t.getKey().endsWith(".ttl"))
141        res.cntType = Manager.FhirFormat.TURTLE;
142      else if (t.getKey().endsWith(".shc"))
143        res.cntType = Manager.FhirFormat.SHC;
144      else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map"))
145        res.cntType = Manager.FhirFormat.TEXT;
146      else
147        throw new FHIRException("Todo: Determining resource type is not yet done");
148    }
149    return res;
150  }
151
152  /**
153   * explore should be true if we're trying to load an -ig parameter, and false if we're loading source
154   *
155   * @throws IOException
156   **/
157  public Map<String, byte[]> loadIgSource(String src,
158                                          boolean recursive,
159                                          boolean explore) throws FHIRException, IOException {
160    // src can be one of the following:
161    // - a canonical url for an ig - this will be converted to a package id and loaded into the cache
162    // - a package id for an ig - this will be loaded into the cache
163    // - a direct reference to a package ("package.tgz") - this will be extracted by the cache manager, but not put in the cache
164    // - a folder containing resources - these will be loaded directly
165    if (Common.isNetworkPath(src)) {
166      String v = null;
167      if (src.contains("|")) {
168        v = src.substring(src.indexOf("|") + 1);
169        src = src.substring(0, src.indexOf("|"));
170      }
171      String pid = explore ? getPackageCacheManager().getPackageId(src) : null;
172      if (!Utilities.noString(pid))
173        return fetchByPackage(pid + (v == null ? "" : "#" + v));
174      else
175        return fetchFromUrl(src + (v == null ? "" : "|" + v), explore);
176    }
177
178    File f = new File(Utilities.path(src));
179    if (f.exists()) {
180      if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists())
181        return loadPackage(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz"));
182      if (f.isDirectory() && new File(Utilities.path(src, "igpack.zip")).exists())
183        return readZip(new FileInputStream(Utilities.path(src, "igpack.zip")));
184      if (f.isDirectory() && new File(Utilities.path(src, "validator.pack")).exists())
185        return readZip(new FileInputStream(Utilities.path(src, "validator.pack")));
186      if (f.isDirectory())
187        return scanDirectory(f, recursive);
188      if (src.endsWith(".tgz"))
189        return loadPackage(new FileInputStream(src), src);
190      if (src.endsWith(".pack"))
191        return readZip(new FileInputStream(src));
192      if (src.endsWith("igpack.zip"))
193        return readZip(new FileInputStream(src));
194      Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(f), src, true);
195      if (fmt != null) {
196        Map<String, byte[]> res = new HashMap<String, byte[]>();
197        res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src));
198        return res;
199      }
200    } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) {
201      return fetchByPackage(src);
202    }
203    throw new FHIRException("Unable to find/resolve/read " + (explore ? "-ig " : "") + src);
204  }
205
206  public void scanForIgVersion(String src,
207                               boolean recursive,
208                               VersionSourceInformation versions) throws Exception {
209    Map<String, byte[]> source = loadIgSourceForVersion(src, recursive, true, versions);
210    if (source != null && source.containsKey("version.info"))
211      versions.see(readInfoVersion(source.get("version.info")), "version.info in " + src);
212  }
213
214  public void scanForVersions(List<String> sources, VersionSourceInformation versions) throws FHIRException, IOException {
215    List<String> refs = new ArrayList<String>();
216    ValidatorUtils.parseSources(sources, refs, context);
217    for (String ref : refs) {
218      Content cnt = loadContent(ref, "validate", false);
219      String s = TextFile.bytesToString(cnt.focus);
220      if (s.contains("http://hl7.org/fhir/3.0")) {
221        versions.see("3.0", "Profile in " + ref);
222      }
223      if (s.contains("http://hl7.org/fhir/1.0")) {
224        versions.see("1.0", "Profile in " + ref);
225      }
226      if (s.contains("http://hl7.org/fhir/4.0")) {
227        versions.see("4.0", "Profile in " + ref);
228      }
229      if (s.contains("http://hl7.org/fhir/1.4")) {
230        versions.see("1.4", "Profile in " + ref);
231      }
232      try {
233        if (s.startsWith("{")) {
234          JsonObject json = JsonTrackingParser.parse(s, null);
235          if (json.has("fhirVersion")) {
236            versions.see(VersionUtilities.getMajMin(JSONUtil.str(json, "fhirVersion")), "fhirVersion in " + ref);
237          }
238        } else {
239          Document doc = ValidatorUtils.parseXml(cnt.focus);
240          String v = XMLUtil.getNamedChildValue(doc.getDocumentElement(), "fhirVersion");
241          if (v != null) {
242            versions.see(VersionUtilities.getMajMin(v), "fhirVersion in " + ref);
243          }
244        }
245      } catch (Exception e) {
246        // nothing
247      }
248    }
249  }
250
251  protected Map<String, byte[]> readZip(InputStream stream) throws IOException {
252    Map<String, byte[]> res = new HashMap<>();
253    ZipInputStream zip = new ZipInputStream(stream);
254    ZipEntry ze;
255    while ((ze = zip.getNextEntry()) != null) {
256      String name = ze.getName();
257      ByteArrayOutputStream b = new ByteArrayOutputStream();
258      int n;
259      byte[] buf = new byte[1024];
260      while ((n = ((InputStream) zip).read(buf, 0, 1024)) > -1) {
261        b.write(buf, 0, n);
262      }
263      res.put(name, b.toByteArray());
264      zip.closeEntry();
265    }
266    zip.close();
267    return res;
268  }
269
270  private String loadPackageForVersion(InputStream stream) throws FHIRException, IOException {
271    return NpmPackage.fromPackage(stream).fhirVersion();
272  }
273
274  private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException {
275    try {
276      SimpleHTTPClient http = new SimpleHTTPClient();
277      HTTPResult res = http.get(source + "?nocache=" + System.currentTimeMillis());
278      res.checkThrowException();
279      return new ByteArrayInputStream(res.getContent());
280    } catch (IOException e) {
281      if (optional)
282        return null;
283      else
284        throw e;
285    }
286  }
287
288  private Map<String, byte[]> loadIgSourceForVersion(String src,
289                                                     boolean recursive,
290                                                     boolean explore,
291                                                     VersionSourceInformation versions) throws FHIRException, IOException {
292    if (Common.isNetworkPath(src)) {
293      String v = null;
294      if (src.contains("|")) {
295        v = src.substring(src.indexOf("|") + 1);
296        src = src.substring(0, src.indexOf("|"));
297      }
298      String pid = getPackageCacheManager().getPackageId(src);
299      if (!Utilities.noString(pid)) {
300        versions.see(fetchVersionByPackage(pid + (v == null ? "" : "#" + v)), "Package " + src);
301        return null;
302      } else {
303        return fetchVersionFromUrl(src + (v == null ? "" : "|" + v), explore, versions);
304      }
305    }
306
307    File f = new File(Utilities.path(src));
308    if (f.exists()) {
309      if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists()) {
310        versions.see(loadPackageForVersion(new FileInputStream(Utilities.path(src, "package.tgz"))), "Package " + src);
311        return null;
312      }
313      if (f.isDirectory() && new File(Utilities.path(src, "igpack.zip")).exists())
314        return readZip(new FileInputStream(Utilities.path(src, "igpack.zip")));
315      if (f.isDirectory() && new File(Utilities.path(src, "validator.pack")).exists())
316        return readZip(new FileInputStream(Utilities.path(src, "validator.pack")));
317      if (f.isDirectory())
318        return scanDirectory(f, recursive);
319      if (src.endsWith(".tgz")) {
320        versions.see(loadPackageForVersion(new FileInputStream(src)), "Package " + src);
321        return null;
322      }
323      if (src.endsWith(".pack"))
324        return readZip(new FileInputStream(src));
325      if (src.endsWith("igpack.zip"))
326        return readZip(new FileInputStream(src));
327      Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(f), src, true);
328      if (fmt != null) {
329        Map<String, byte[]> res = new HashMap<String, byte[]>();
330        res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src));
331        return res;
332      }
333    } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) {
334      versions.see(fetchVersionByPackage(src), "Package " + src);
335      return null;
336    }
337    throw new FHIRException("Unable to find/resolve/read -ig " + src);
338  }
339
340
341  private Map<String, byte[]> fetchByPackage(String src) throws FHIRException, IOException {
342    String id = src;
343    String version = null;
344    if (src.contains("#")) {
345      id = src.substring(0, src.indexOf("#"));
346      version = src.substring(src.indexOf("#") + 1);
347    }
348    if (version == null) {
349      version = getPackageCacheManager().getLatestVersion(id);
350    }
351    NpmPackage pi;
352    if (version == null) {
353      pi = getPackageCacheManager().loadPackageFromCacheOnly(id);
354      if (pi != null)
355        System.out.println("   ... Using version " + pi.version());
356    } else
357      pi = getPackageCacheManager().loadPackageFromCacheOnly(id, version);
358    if (pi == null) {
359      return resolvePackage(id, version);
360    } else
361      return loadPackage(pi);
362  }
363
364  private Map<String, byte[]> loadPackage(InputStream stream, String name) throws FHIRException, IOException {
365    return loadPackage(NpmPackage.fromPackage(stream));
366  }
367
368  public Map<String, byte[]> loadPackage(NpmPackage pi) throws FHIRException, IOException {
369    getContext().getLoadedPackages().add(pi.name() + "#" + pi.version());
370    Map<String, byte[]> res = new HashMap<String, byte[]>();
371    for (String s : pi.dependencies()) {
372      if (s.endsWith(".x") && s.length() > 2) {
373        String packageMajorMinor = s.substring(0, s.length() - 2);
374        boolean found = false;
375        for (int i = 0; i < getContext().getLoadedPackages().size() && !found; ++i) {
376          String loadedPackage = getContext().getLoadedPackages().get(i);
377          if (loadedPackage.startsWith(packageMajorMinor)) {
378            found = true;
379          }
380        }
381        if (found)
382          continue;
383      }
384      if (!getContext().getLoadedPackages().contains(s)) {
385        if (!VersionUtilities.isCorePackage(s)) {
386          System.out.println("+  .. load IG from " + s);
387          res.putAll(fetchByPackage(s));
388        }
389      }
390    }
391
392    for (String s : pi.listResources("CodeSystem", "ConceptMap", "ImplementationGuide", "CapabilityStatement", "SearchParameter", "Conformance", "StructureMap", "ValueSet", "StructureDefinition")) {
393      res.put(s, TextFile.streamToBytes(pi.load("package", s)));
394    }
395    String ini = "[FHIR]\r\nversion=" + pi.fhirVersion() + "\r\n";
396    res.put("version.info", ini.getBytes());
397    return res;
398  }
399
400  private Map<String, byte[]> resolvePackage(String id, String v) throws FHIRException, IOException {
401    NpmPackage pi = getPackageCacheManager().loadPackage(id, v);
402    if (pi != null && v == null)
403      System.out.println("   ... Using version " + pi.version());
404    return loadPackage(pi);
405  }
406
407  private String readInfoVersion(byte[] bs) throws IOException {
408    String is = TextFile.bytesToString(bs);
409    is = is.trim();
410    IniFile ini = new IniFile(new ByteArrayInputStream(TextFile.stringToBytes(is, false)));
411    return ini.getStringProperty("FHIR", "version");
412  }
413
414  private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional, List<String> errors) throws FHIRException, IOException {
415    try {
416      SimpleHTTPClient http = new SimpleHTTPClient();
417      try {
418        // try with cache-busting option and then try withhout in case the server doesn't support that
419        HTTPResult res = http.get(source + "?nocache=" + System.currentTimeMillis(), contentType);
420        res.checkThrowException();
421        return res.getContent();
422      } catch (Exception e) {
423        HTTPResult res = http.get(source, contentType);
424        res.checkThrowException();
425        return res.getContent();
426      }
427    } catch (IOException e) {
428      if (errors != null) {
429        errors.add("Error accessing " + source + ": " + e.getMessage());
430      }
431      if (optional)
432        return null;
433      else
434        throw e;
435    }
436  }
437
438  private Map<String, byte[]> fetchVersionFromUrl(String src,
439                                                  boolean explore,
440                                                  VersionSourceInformation versions) throws FHIRException, IOException {
441    if (src.endsWith(".tgz")) {
442      versions.see(loadPackageForVersion(fetchFromUrlSpecific(src, false)), "From Package " + src);
443      return null;
444    }
445    if (src.endsWith(".pack"))
446      return readZip(fetchFromUrlSpecific(src, false));
447    if (src.endsWith("igpack.zip"))
448      return readZip(fetchFromUrlSpecific(src, false));
449
450    InputStream stream = null;
451    if (explore) {
452      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "package.tgz"), true);
453      if (stream != null) {
454        versions.see(loadPackageForVersion(stream), "From Package at " + src);
455        return null;
456      }
457      // todo: these options are deprecated - remove once all IGs have been rebuilt post R4 technical correction
458      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "igpack.zip"), true);
459      if (stream != null)
460        return readZip(stream);
461      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true);
462      if (stream != null)
463        return readZip(stream);
464      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true);
465      //// -----
466    }
467
468    // ok, having tried all that... now we'll just try to access it directly
469    byte[] cnt;
470    if (stream == null)
471      cnt = fetchFromUrlSpecific(src, "application/json", true, null);
472    else
473      cnt = TextFile.streamToBytes(stream);
474
475    Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), cnt, src, true);
476    if (fmt != null) {
477      Map<String, byte[]> res = new HashMap<String, byte[]>();
478      res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt);
479      return res;
480    }
481    String fn = Utilities.path("[tmp]", "fetch-resource-error-content.bin");
482    TextFile.bytesToFile(cnt, fn);
483    System.out.println("Error Fetching " + src);
484    System.out.println("Some content was found, saved to " + fn);
485    System.out.println("1st 100 bytes = " + presentForDebugging(cnt));
486    throw new FHIRException("Unable to find/resolve/read " + (explore ? "-ig " : "") + src);
487  }
488
489  private String fetchVersionByPackage(String src) throws FHIRException, IOException {
490    String id = src;
491    String version = null;
492    if (src.contains("#")) {
493      id = src.substring(0, src.indexOf("#"));
494      version = src.substring(src.indexOf("#") + 1);
495    }
496    if (version == null) {
497      version = getPackageCacheManager().getLatestVersion(id);
498    }
499    NpmPackage pi = null;
500    if (version == null) {
501      pi = getPackageCacheManager().loadPackageFromCacheOnly(id);
502      if (pi != null)
503        System.out.println("   ... Using version " + pi.version());
504    } else
505      pi = getPackageCacheManager().loadPackageFromCacheOnly(id, version);
506    if (pi == null) {
507      return resolvePackageForVersion(id, version);
508    } else {
509      return pi.fhirVersion();
510    }
511  }
512
513  private Map<String, byte[]> fetchFromUrl(String src, boolean explore) throws FHIRException, IOException {
514    if (src.endsWith(".tgz"))
515      return loadPackage(fetchFromUrlSpecific(src, false), src);
516    if (src.endsWith(".pack"))
517      return readZip(fetchFromUrlSpecific(src, false));
518    if (src.endsWith("igpack.zip"))
519      return readZip(fetchFromUrlSpecific(src, false));
520
521    InputStream stream = null;
522    if (explore) {
523      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "package.tgz"), true);
524      if (stream != null)
525        return loadPackage(stream, Utilities.pathURL(src, "package.tgz"));
526      // todo: these options are deprecated - remove once all IGs have been rebuilt post R4 technical correction
527      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "igpack.zip"), true);
528      if (stream != null)
529        return readZip(stream);
530      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true);
531      if (stream != null)
532        return readZip(stream);
533      stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true);
534      //// -----
535    }
536
537    // ok, having tried all that... now we'll just try to access it directly
538    byte[] cnt;
539    List<String> errors = new ArrayList<>();
540    if (stream != null) {
541      cnt = TextFile.streamToBytes(stream);
542    } else {
543      cnt = fetchFromUrlSpecific(src, "application/json", true, errors);
544      if (cnt == null) {
545        cnt = fetchFromUrlSpecific(src, "application/xml", true, errors);
546      }
547    }
548    if (cnt == null) {
549      throw new FHIRException("Unable to fetch content from " + src + " (" + errors.toString() + ")");
550
551    }
552    Manager.FhirFormat fmt = checkFormat(cnt, src);
553    if (fmt != null) {
554      Map<String, byte[]> res = new HashMap<>();
555      res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt);
556      return res;
557    }
558    throw new FHIRException("Unable to read content from " + src + ": cannot determine format");
559  }
560
561  private boolean isIgnoreFile(File ff) {
562    if (ff.getName().startsWith(".") || ff.getAbsolutePath().contains(".git")) {
563      return true;
564    }
565    return Utilities.existsInList(Utilities.getFileExtension(ff.getName()).toLowerCase(), IGNORED_EXTENSIONS);
566  }
567
568  private Map<String, byte[]> scanDirectory(File f, boolean recursive) throws IOException {
569    Map<String, byte[]> res = new HashMap<>();
570    for (File ff : f.listFiles()) {
571      if (ff.isDirectory() && recursive) {
572        res.putAll(scanDirectory(ff, true));
573      } else if (!ff.isDirectory() && !isIgnoreFile(ff)) {
574        Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(ff), ff.getAbsolutePath(), true);
575        if (fmt != null) {
576          res.put(Utilities.changeFileExt(ff.getName(), "." + fmt.getExtension()), TextFile.fileToBytes(ff.getAbsolutePath()));
577        }
578      }
579    }
580    return res;
581  }
582
583  private String resolvePackageForVersion(String id, String v) throws FHIRException, IOException {
584    NpmPackage pi = getPackageCacheManager().loadPackage(id, v);
585    return pi.fhirVersion();
586  }
587
588  private String presentForDebugging(byte[] cnt) {
589    StringBuilder b = new StringBuilder();
590    for (int i = 0; i < Integer.min(cnt.length, 50); i++) {
591      b.append(Integer.toHexString(cnt[i]));
592    }
593    return b.toString();
594  }
595
596  private Manager.FhirFormat checkFormat(byte[] cnt, String filename) {
597    System.out.println("   ..Detect format for " + filename);
598    try {
599      JsonTrackingParser.parseJson(cnt);
600      return Manager.FhirFormat.JSON;
601    } catch (Exception e) {
602      log("Not JSON: " + e.getMessage());
603    }
604    try {
605      ValidatorUtils.parseXml(cnt);
606      return Manager.FhirFormat.XML;
607    } catch (Exception e) {
608      log("Not XML: " + e.getMessage());
609    }
610    try {
611      new Turtle().parse(TextFile.bytesToString(cnt));
612      return Manager.FhirFormat.TURTLE;
613    } catch (Exception e) {
614      log("Not Turtle: " + e.getMessage());
615    }
616    try {
617      new StructureMapUtilities(getContext(), null, null).parse(TextFile.bytesToString(cnt), null);
618      return Manager.FhirFormat.TEXT;
619    } catch (Exception e) {
620      log("Not Text: " + e.getMessage());
621    }
622    log("     .. not a resource: " + filename);
623    return null;
624  }
625
626  private boolean exemptFile(String fn) {
627    return Utilities.existsInList(fn, EXEMPT_FILES);
628  }
629
630  private Resource loadFileWithErrorChecking(String version, Map.Entry<String, byte[]> t, String fn) {
631    log("* load file: " + fn);
632    Resource r = null;
633    try {
634      r = loadResourceByVersion(version, t.getValue(), fn);
635      log(" .. success");
636    } catch (Exception e) {
637      if (!isDebug()) {
638        System.out.print("* load file: " + fn);
639      }
640      System.out.println(" - ignored due to error: " + (e.getMessage() == null ? " (null - NPE)" : e.getMessage()));
641      if (isDebug() || ((e.getMessage() != null && e.getMessage().contains("cannot be cast")))) {
642        e.printStackTrace();
643      }
644    }
645    return r;
646  }
647
648  public Resource loadResourceByVersion(String fhirVersion, byte[] content, String fn) throws IOException, FHIRException {
649    Resource r;
650    if (fhirVersion.startsWith("3.0")) {
651      org.hl7.fhir.dstu3.model.Resource res;
652      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
653        res = new org.hl7.fhir.dstu3.formats.XmlParser().parse(new ByteArrayInputStream(content));
654      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
655        res = new org.hl7.fhir.dstu3.formats.JsonParser().parse(new ByteArrayInputStream(content));
656      else if (fn.endsWith(".txt") || fn.endsWith(".map"))
657        res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content));
658      else
659        throw new FHIRException("Unsupported format for " + fn);
660      r = VersionConvertorFactory_30_50.convertResource(res);
661    } else if (fhirVersion.startsWith("4.0")) {
662      org.hl7.fhir.r4.model.Resource res;
663      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
664        res = new org.hl7.fhir.r4.formats.XmlParser().parse(new ByteArrayInputStream(content));
665      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
666        res = new org.hl7.fhir.r4.formats.JsonParser().parse(new ByteArrayInputStream(content));
667      else if (fn.endsWith(".txt") || fn.endsWith(".map"))
668        res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn);
669      else
670        throw new FHIRException("Unsupported format for " + fn);
671      r = VersionConvertorFactory_40_50.convertResource(res);
672    } else if (fhirVersion.startsWith("1.4")) {
673      org.hl7.fhir.dstu2016may.model.Resource res;
674      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
675        res = new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(new ByteArrayInputStream(content));
676      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
677        res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new ByteArrayInputStream(content));
678      else
679        throw new FHIRException("Unsupported format for " + fn);
680      r = VersionConvertorFactory_14_50.convertResource(res);
681    } else if (fhirVersion.startsWith("1.0")) {
682      org.hl7.fhir.dstu2.model.Resource res;
683      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
684        res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content));
685      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
686        res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content));
687      else
688        throw new FHIRException("Unsupported format for " + fn);
689      r = VersionConvertorFactory_10_50.convertResource(res, new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5());
690    } else if (fhirVersion.equals(Constants.VERSION) || "current".equals(fhirVersion)) {
691      if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
692        r = new XmlParser().parse(new ByteArrayInputStream(content));
693      else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
694        r = new JsonParser().parse(new ByteArrayInputStream(content));
695      else if (fn.endsWith(".txt"))
696        r = new StructureMapUtilities(getContext(), null, null).parse(TextFile.bytesToString(content), fn);
697      else if (fn.endsWith(".map"))
698        r = new StructureMapUtilities(null).parse(new String(content), fn);
699      else
700        throw new FHIRException("Unsupported format for " + fn);
701    } else
702      throw new FHIRException("Unsupported version " + fhirVersion);
703    return r;
704  }
705
706  private void log(String s) {
707    if (isDebug()) System.out.println(s);
708  }
709}