001package org.hl7.fhir.utilities.npm; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.BufferedOutputStream; 035import java.io.ByteArrayInputStream; 036import java.io.ByteArrayOutputStream; 037import java.io.File; 038import java.io.FileInputStream; 039import java.io.FileNotFoundException; 040import java.io.IOException; 041import java.io.InputStream; 042import java.io.OutputStream; 043import java.nio.charset.Charset; 044import java.nio.charset.StandardCharsets; 045import java.util.ArrayList; 046import java.util.Collections; 047import java.util.Comparator; 048import java.util.HashMap; 049import java.util.List; 050import java.util.Map; 051import java.util.Map.Entry; 052import java.util.Set; 053import java.util.zip.ZipEntry; 054import java.util.zip.ZipInputStream; 055 056import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 057import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 058import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; 059import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 060import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 061import org.hl7.fhir.exceptions.FHIRException; 062import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 063import org.hl7.fhir.utilities.TextFile; 064import org.hl7.fhir.utilities.Utilities; 065import org.hl7.fhir.utilities.json.JSONUtil; 066import org.hl7.fhir.utilities.json.JsonTrackingParser; 067import org.hl7.fhir.utilities.npm.NpmPackage.ITransformingLoader; 068import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformationSorter; 069import org.hl7.fhir.utilities.npm.PackageGenerator.PackageType; 070 071import com.google.gson.GsonBuilder; 072import com.google.gson.JsonArray; 073import com.google.gson.JsonElement; 074import com.google.gson.JsonObject; 075 076/** 077 * info and loader for a package 078 * 079 * Packages may exist on disk in the cache, or purely in memory when they are loaded on the fly 080 * 081 * Packages are contained in subfolders (see the package spec). The FHIR resources will be in "package" 082 * 083 * @author Grahame Grieve 084 * 085 */ 086public class NpmPackage { 087 088 public interface ITransformingLoader { 089 090 byte[] load(File f); 091 092 } 093 094 public class PackageResourceInformationSorter implements Comparator<PackageResourceInformation> { 095 @Override 096 public int compare(PackageResourceInformation o1, PackageResourceInformation o2) { 097 return o1.filename.compareTo(o2.filename); 098 } 099 } 100 101 public class PackageResourceInformation { 102 private String id; 103 private String type; 104 private String url; 105 private String version; 106 private String filename; 107 private String supplements; 108 109 public PackageResourceInformation(String root, JsonObject fi) throws IOException { 110 super(); 111 id = JSONUtil.str(fi, "id"); 112 type = JSONUtil.str(fi, "resourceType"); 113 url = JSONUtil.str(fi, "url"); 114 version = JSONUtil.str(fi, "version"); 115 filename = Utilities.path(root, JSONUtil.str(fi, "filename")); 116 supplements = JSONUtil.str(fi, "supplements"); 117 } 118 public String getId() { 119 return id; 120 } 121 public String getType() { 122 return type; 123 } 124 public String getUrl() { 125 return url; 126 } 127 public String getVersion() { 128 return version; 129 } 130 public String getFilename() { 131 return filename; 132 } 133 public String getSupplements() { 134 return supplements; 135 } 136 137 } 138 public class IndexVersionSorter implements Comparator<JsonObject> { 139 140 @Override 141 public int compare(JsonObject o0, JsonObject o1) { 142 String v0 = JSONUtil.str(o0, "version"); 143 String v1 = JSONUtil.str(o1, "version"); 144 return v0.compareTo(v1); 145 } 146 } 147 148 public static boolean isValidName(String pid) { 149 return pid.matches("^[a-z][a-zA-Z0-9]*(\\.[a-z][a-zA-Z0-9\\-]*)+$"); 150 } 151 152 public static boolean isValidVersion(String ver) { 153 return ver.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$"); 154 } 155 156 public class NpmPackageFolder { 157 private String name; 158 private Map<String, List<String>> types = new HashMap<>(); 159 private Map<String, byte[]> content = new HashMap<>(); 160 private JsonObject index; 161 private File folder; 162 163 public NpmPackageFolder(String name) { 164 super(); 165 this.name = name; 166 } 167 168 public Map<String, List<String>> getTypes() { 169 return types; 170 } 171 172 public String getName() { 173 return name; 174 } 175 176 public boolean readIndex(JsonObject index) { 177 if (!index.has("index-version") || (index.get("index-version").getAsInt() != 1)) { 178 return false; 179 } 180 this.index = index; 181 for (JsonElement e : index.getAsJsonArray("files")) { 182 JsonObject file = (JsonObject) e; 183 String type = JSONUtil.str(file, "resourceType"); 184 String name = JSONUtil.str(file, "filename"); 185 if (!types.containsKey(type)) 186 types.put(type, new ArrayList<>()); 187 types.get(type).add(name); 188 } 189 return true; 190 } 191 192 public List<String> listFiles() { 193 List<String> res = new ArrayList<>(); 194 if (folder != null) { 195 for (File f : folder.listFiles()) { 196 if (!f.isDirectory() && !Utilities.existsInList(f.getName(), "package.json", ".index.json")) { 197 res.add(f.getName()); 198 } 199 } 200 } else { 201 for (String s : content.keySet()) { 202 if (!Utilities.existsInList(s, "package.json", ".index.json")) { 203 res.add(s); 204 } 205 } 206 } 207 Collections.sort(res); 208 return res; 209 } 210 211 public Map<String, byte[]> getContent() { 212 return content; 213 } 214 215 public byte[] fetchFile(String file) throws FileNotFoundException, IOException { 216 if (folder != null) { 217 File f = new File(Utilities.path(folder.getAbsolutePath(), file)); 218 if (f.exists()) { 219 return TextFile.fileToBytes(f); 220 } else { 221 return null; 222 } 223 } else { 224 return content.get(file); 225 } 226 } 227 228 public boolean hasFile(String file) throws IOException { 229 if (folder != null) { 230 return new File(Utilities.path(folder.getAbsolutePath(), file)).exists(); 231 } else { 232 return content.containsKey(file); 233 } 234 235 } 236 237 public String dump() { 238 return name + " ("+ (folder == null ? "null" : folder.toString())+") | "+Boolean.toString(index != null)+" | "+content.size()+" | "+types.size(); 239 } 240 241 public void removeFile(String n) throws IOException { 242 if (folder != null) { 243 new File(Utilities.path(folder.getAbsolutePath(), n)).delete(); 244 } else { 245 content.remove(n); 246 } 247 changedByLoader = true; 248 } 249 250 } 251 252 private String path; 253 private JsonObject npm; 254 private Map<String, NpmPackageFolder> folders = new HashMap<>(); 255 private boolean changedByLoader; // internal qa only! 256 private Map<String, Object> userData = new HashMap<>(); 257 258 /** 259 * Constructor 260 */ 261 private NpmPackage() { 262 super(); 263 } 264 265 /** 266 * Factory method that parses a package from an extracted folder 267 */ 268 public static NpmPackage fromFolder(String path) throws IOException { 269 NpmPackage res = new NpmPackage(); 270 res.loadFiles(path, new File(path)); 271 res.checkIndexed(path); 272 return res; 273 } 274 275 /** 276 * Factory method that starts a new empty package using the given PackageGenerator to create the manifest 277 */ 278 public static NpmPackage empty(PackageGenerator thePackageGenerator) { 279 NpmPackage retVal = new NpmPackage(); 280 retVal.npm = thePackageGenerator.getRootJsonObject(); 281 return retVal; 282 } 283 284 public Map<String, Object> getUserData() { 285 return userData; 286 } 287 288 public void loadFiles(String path, File source, String... exemptions) throws FileNotFoundException, IOException { 289 this.npm = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(Utilities.path(path, "package", "package.json"))); 290 this.path = path; 291 292 File dir = new File(path); 293 for (File f : dir.listFiles()) { 294 if (!isInternalExemptFile(f) && !Utilities.existsInList(f.getName(), exemptions)) { 295 if (f.isDirectory()) { 296 String d = f.getName(); 297 if (!d.equals("package")) { 298 d = Utilities.path("package", d); 299 } 300 NpmPackageFolder folder = this.new NpmPackageFolder(d); 301 folder.folder = f; 302 this.folders.put(d, folder); 303 File ij = new File(Utilities.path(f.getAbsolutePath(), ".index.json")); 304 if (ij.exists()) { 305 try { 306 if (!folder.readIndex(JsonTrackingParser.parseJson(ij))) { 307 indexFolder(folder.getName(), folder); 308 } 309 } catch (Exception e) { 310 throw new IOException("Error parsing "+ij.getAbsolutePath()+": "+e.getMessage(), e); 311 } 312 } 313 loadSubFolders(dir.getAbsolutePath(), f); 314 } else { 315 NpmPackageFolder folder = this.new NpmPackageFolder(Utilities.path("package", "$root")); 316 folder.folder = dir; 317 this.folders.put(Utilities.path("package", "$root"), folder); 318 } 319 } 320 } 321 } 322 323 public static boolean isInternalExemptFile(File f) { 324 return Utilities.existsInList(f.getName(), ".git", ".svn") || Utilities.existsInList(f.getName(), "package-list.json"); 325 } 326 327 private void loadSubFolders(String rootPath, File dir) throws IOException { 328 for (File f : dir.listFiles()) { 329 if (f.isDirectory()) { 330 String d = f.getAbsolutePath().substring(rootPath.length()+1); 331 if (!d.startsWith("package")) { 332 d = Utilities.path("package", d); 333 } 334 NpmPackageFolder folder = this.new NpmPackageFolder(d); 335 folder.folder = f; 336 this.folders.put(d, folder); 337 File ij = new File(Utilities.path(f.getAbsolutePath(), ".index.json")); 338 if (ij.exists()) { 339 try { 340 if (!folder.readIndex(JsonTrackingParser.parseJson(ij))) { 341 indexFolder(folder.getName(), folder); 342 } 343 } catch (Exception e) { 344 throw new IOException("Error parsing "+ij.getAbsolutePath()+": "+e.getMessage(), e); 345 } 346 } 347 loadSubFolders(rootPath, f); 348 } 349 } 350 } 351 352 public static NpmPackage fromFolder(String folder, PackageType defType, String... exemptions) throws IOException { 353 NpmPackage res = new NpmPackage(); 354 res.loadFiles(folder, new File(folder), exemptions); 355 if (!res.folders.containsKey("package")) { 356 res.folders.put("package", res.new NpmPackageFolder("package")); 357 } 358 if (!res.folders.get("package").hasFile("package.json") && defType != null) { 359 TextFile.stringToFile("{ \"type\" : \""+defType.getCode()+"\"}", Utilities.path(res.folders.get("package").folder.getAbsolutePath(), "package.json")); 360 } 361 res.npm = (JsonObject) new com.google.gson.JsonParser().parse(new String(res.folders.get("package").fetchFile("package.json"))); 362 return res; 363 } 364 365 private static final int BUFFER_SIZE = 1024; 366 367 public static NpmPackage fromPackage(InputStream tgz) throws IOException { 368 return fromPackage(tgz, null, false); 369 } 370 371 public static NpmPackage fromPackage(InputStream tgz, String desc) throws IOException { 372 return fromPackage(tgz, desc, false); 373 } 374 375 public static NpmPackage fromPackage(InputStream tgz, String desc, boolean progress) throws IOException { 376 NpmPackage res = new NpmPackage(); 377 res.readStream(tgz, desc, progress); 378 return res; 379 } 380 381 public void readStream(InputStream tgz, String desc, boolean progress) throws IOException { 382 GzipCompressorInputStream gzipIn; 383 try { 384 gzipIn = new GzipCompressorInputStream(tgz); 385 } catch (Exception e) { 386 throw new IOException("Error reading "+(desc == null ? "package" : desc)+": "+e.getMessage(), e); 387 } 388 try (TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn)) { 389 TarArchiveEntry entry; 390 391 int i = 0; 392 int c = 12; 393 while ((entry = (TarArchiveEntry) tarIn.getNextEntry()) != null) { 394 i++; 395 String n = entry.getName(); 396 if (entry.isDirectory()) { 397 String dir = n.substring(0, n.length()-1); 398 if (dir.startsWith("package/")) { 399 dir = dir.substring(8); 400 } 401 folders.put(dir, new NpmPackageFolder(dir)); 402 } else { 403 int count; 404 byte data[] = new byte[BUFFER_SIZE]; 405 ByteArrayOutputStream fos = new ByteArrayOutputStream(); 406 try (BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER_SIZE)) { 407 while ((count = tarIn.read(data, 0, BUFFER_SIZE)) != -1) { 408 dest.write(data, 0, count); 409 } 410 } 411 fos.close(); 412 loadFile(n, fos.toByteArray()); 413 } 414 if (progress && i % 50 == 0) { 415 c++; 416 System.out.print("."); 417 if (c == 120) { 418 System.out.println(""); 419 System.out.print(" "); 420 c = 2; 421 } 422 } 423 } 424 } 425 try { 426 npm = JsonTrackingParser.parseJson(folders.get("package").fetchFile("package.json")); 427 } catch (Exception e) { 428 throw new IOException("Error parsing "+(desc == null ? "" : desc+"#")+"package/package.json: "+e.getMessage(), e); 429 } 430 checkIndexed(desc); 431 } 432 433 public void loadFile(String n, byte[] data) throws IOException { 434 String dir = n.contains("/") ? n.substring(0, n.lastIndexOf("/")) : "$root"; 435 if (dir.startsWith("package/")) { 436 dir = dir.substring(8); 437 } 438 n = n.substring(n.lastIndexOf("/")+1); 439 NpmPackageFolder index = folders.get(dir); 440 if (index == null) { 441 index = new NpmPackageFolder(dir); 442 folders.put(dir, index); 443 } 444 index.content.put(n, data); 445 } 446 447 private void checkIndexed(String desc) throws IOException { 448 for (NpmPackageFolder folder : folders.values()) { 449 if (folder.index == null) { 450 indexFolder(desc, folder); 451 } 452 } 453 } 454 455 public void indexFolder(String desc, NpmPackageFolder folder) throws FileNotFoundException, IOException { 456 List<String> remove = new ArrayList<>(); 457 NpmPackageIndexBuilder indexer = new NpmPackageIndexBuilder(); 458 indexer.start(); 459 for (String n : folder.listFiles()) { 460 if (!indexer.seeFile(n, folder.fetchFile(n))) { 461 remove.add(n); 462 } 463 } 464 for (String n : remove) { 465 folder.removeFile(n); 466 } 467 String json = indexer.build(); 468 try { 469 folder.readIndex(JsonTrackingParser.parseJson(json)); 470 if (folder.folder != null) { 471 TextFile.stringToFile(json, Utilities.path(folder.folder.getAbsolutePath(), ".index.json")); 472 } 473 } catch (Exception e) { 474 TextFile.stringToFile(json, Utilities.path("[tmp]", ".index.json")); 475 throw new IOException("Error parsing "+(desc == null ? "" : desc+"#")+"package/"+folder.name+"/.index.json: "+e.getMessage(), e); 476 } 477 } 478 479 480 public static NpmPackage fromZip(InputStream stream, boolean dropRootFolder, String desc) throws IOException { 481 NpmPackage res = new NpmPackage(); 482 ZipInputStream zip = new ZipInputStream(stream); 483 ZipEntry ze; 484 while ((ze = zip.getNextEntry()) != null) { 485 int size; 486 byte[] buffer = new byte[2048]; 487 488 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 489 BufferedOutputStream bos = new BufferedOutputStream(bytes, buffer.length); 490 491 while ((size = zip.read(buffer, 0, buffer.length)) != -1) { 492 bos.write(buffer, 0, size); 493 } 494 bos.flush(); 495 bos.close(); 496 if (bytes.size() > 0) { 497 if (dropRootFolder) { 498 res.loadFile(ze.getName().substring(ze.getName().indexOf("/")+1), bytes.toByteArray()); 499 } else { 500 res.loadFile(ze.getName(), bytes.toByteArray()); 501 } 502 } 503 zip.closeEntry(); 504 } 505 zip.close(); 506 try { 507 res.npm = JsonTrackingParser.parseJson(res.folders.get("package").fetchFile("package.json")); 508 } catch (Exception e) { 509 throw new IOException("Error parsing "+(desc == null ? "" : desc+"#")+"package/package.json: "+e.getMessage(), e); 510 } 511 res.checkIndexed(desc); 512 return res; 513 } 514 515 516 /** 517 * Accessing the contents of the package - get a list of files in a subfolder of the package 518 * 519 * @param folder 520 * @return 521 * @throws IOException 522 */ 523 public List<String> list(String folder) throws IOException { 524 List<String> res = new ArrayList<String>(); 525 if (folders.containsKey(folder)) { 526 res.addAll(folders.get(folder).listFiles()); 527 } else if (folders.containsKey(Utilities.path("package", folder))) { 528 res.addAll(folders.get(Utilities.path("package", folder)).listFiles()); 529 } 530 return res; 531 } 532 533 public List<String> listResources(String... types) throws IOException { 534 List<String> res = new ArrayList<String>(); 535 NpmPackageFolder folder = folders.get("package"); 536 for (String s : types) { 537 if (folder.types.containsKey(s)) 538 res.addAll(folder.types.get(s)); 539 } 540 Collections.sort(res); 541 return res; 542 } 543 544 public List<PackageResourceInformation> listIndexedResources(String... types) throws IOException { 545 List<PackageResourceInformation> res = new ArrayList<PackageResourceInformation>(); 546 for (NpmPackageFolder folder : folders.values()) { 547 if (folder.index != null) { 548 for (JsonElement e : folder.index.getAsJsonArray("files")) { 549 JsonObject fi = e.getAsJsonObject(); 550 if (Utilities.existsInList(JSONUtil.str(fi, "resourceType"), types)) { 551 res.add(new PackageResourceInformation(folder.folder.getAbsolutePath(), fi)); 552 } 553 } 554 } 555 } 556 // Collections.sort(res, new PackageResourceInformationSorter()); 557 return res; 558 } 559 560 /** 561 * use the name from listResources() 562 * 563 * @param id 564 * @return 565 * @throws IOException 566 */ 567 public InputStream loadResource(String file) throws IOException { 568 NpmPackageFolder folder = folders.get("package"); 569 return new ByteArrayInputStream(folder.fetchFile(file)); 570 } 571 572 /** 573 * get a stream that contains the contents of a resource in the base folder, by it's canonical URL 574 * 575 * @param url - the canonical URL of the resource (exact match only) 576 * @return null if it is not found 577 * @throws IOException 578 */ 579 public InputStream loadByCanonical(String canonical) throws IOException { 580 return loadByCanonicalVersion("package", canonical, null); 581 } 582 583 /** 584 * get a stream that contains the contents of a resource in the nominated folder, by it's canonical URL 585 * 586 * @param folder - one of the folders in the package (main folder is "package") 587 * @param url - the canonical URL of the resource (exact match only) 588 * @return null if it is not found 589 * @throws IOException 590 */ 591 public InputStream loadByCanonical(String folder, String canonical) throws IOException { 592 return loadByCanonicalVersion(folder, canonical, null); 593 } 594 595 /** 596 * get a stream that contains the contents of a resource in the base folder, by it's canonical URL 597 * 598 * @param url - the canonical URL of the resource (exact match only) 599 * @param version - the specified version (or null if the most recent) 600 * 601 * @return null if it is not found 602 * @throws IOException 603 */ 604 public InputStream loadByCanonicalVersion(String canonical, String version) throws IOException { 605 return loadByCanonicalVersion("package", canonical, version); 606 } 607 608 /** 609 * get a stream that contains the contents of a resource in the nominated folder, by it's canonical URL 610 * 611 * @param folder - one of the folders in the package (main folder is "package") 612 * @param url - the canonical URL of the resource (exact match only) 613 * @param version - the specified version (or null if the most recent) 614 * 615 * @return null if it is not found 616 * @throws IOException 617 */ 618 public InputStream loadByCanonicalVersion(String folder, String canonical, String version) throws IOException { 619 NpmPackageFolder f = folders.get(folder); 620 List<JsonObject> matches = new ArrayList<>(); 621 for (JsonElement e : f.index.getAsJsonArray("files")) { 622 JsonObject file = (JsonObject) e; 623 if (canonical.equals(JSONUtil.str(file, "url"))) { 624 if (version != null && version.equals(JSONUtil.str(file, "version"))) { 625 return load("package", JSONUtil.str(file, "filename")); 626 } else if (version == null) { 627 matches.add(file); 628 } 629 } 630 if (matches.size() > 0) { 631 if (matches.size() == 1) { 632 return load("package", JSONUtil.str(matches.get(0), "filename")); 633 } else { 634 Collections.sort(matches, new IndexVersionSorter()); 635 return load("package", JSONUtil.str(matches.get(matches.size()-1), "filename")); 636 } 637 } 638 } 639 return null; 640 } 641 642 /** 643 * get a stream that contains the contents of one of the files in the base package 644 * 645 * @param file 646 * @return 647 * @throws IOException 648 */ 649 public InputStream load(String file) throws IOException { 650 return load("package", file); 651 } 652 /** 653 * get a stream that contains the contents of one of the files in a folder 654 * 655 * @param folder 656 * @param file 657 * @return 658 * @throws IOException 659 */ 660 public InputStream load(String folder, String file) throws IOException { 661 NpmPackageFolder f = folders.get(folder); 662 if (f == null) { 663 f = folders.get(Utilities.path("package", folder)); 664 } 665 if (f != null && f.hasFile(file)) { 666 return new ByteArrayInputStream(f.fetchFile(file)); 667 } else { 668 throw new IOException("Unable to find the file "+folder+"/"+file+" in the package "+name()); 669 } 670 } 671 672 public boolean hasFile(String folder, String file) throws IOException { 673 NpmPackageFolder f = folders.get(folder); 674 if (f == null) { 675 f = folders.get(Utilities.path("package", folder)); 676 } 677 return f != null && f.hasFile(file); 678 } 679 680 681 /** 682 * Handle to the package json file 683 * 684 * @return 685 */ 686 public JsonObject getNpm() { 687 return npm; 688 } 689 690 /** 691 * convenience method for getting the package name 692 * @return 693 */ 694 public String name() { 695 return JSONUtil.str(npm, "name"); 696 } 697 698 /** 699 * convenience method for getting the package id (which in NPM language is the same as the name) 700 * @return 701 */ 702 public String id() { 703 return JSONUtil.str(npm, "name"); 704 } 705 706 public String date() { 707 return JSONUtil.str(npm, "date"); 708 } 709 710 public String canonical() { 711 return JSONUtil.str(npm, "canonical"); 712 } 713 714 /** 715 * convenience method for getting the package version 716 * @return 717 */ 718 public String version() { 719 return JSONUtil.str(npm, "version"); 720 } 721 722 /** 723 * convenience method for getting the package fhir version 724 * @return 725 */ 726 public String fhirVersion() { 727 if ("hl7.fhir.core".equals(JSONUtil.str(npm, "name"))) 728 return JSONUtil.str(npm, "version"); 729 else if (JSONUtil.str(npm, "name").startsWith("hl7.fhir.r2.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r2b.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r3.") || 730 JSONUtil.str(npm, "name").startsWith("hl7.fhir.r4.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r4b.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r5.")) 731 return JSONUtil.str(npm, "version"); 732 else { 733 JsonObject dep = null; 734 if (npm.has("dependencies") && npm.get("dependencies").isJsonObject()) { 735 dep = npm.getAsJsonObject("dependencies"); 736 if (dep != null) { 737 for (Entry<String, JsonElement> e : dep.entrySet()) { 738 if (Utilities.existsInList(e.getKey(), "hl7.fhir.r2.core", "hl7.fhir.r2b.core", "hl7.fhir.r3.core", "hl7.fhir.r4.core")) 739 return e.getValue().getAsString(); 740 if (Utilities.existsInList(e.getKey(), "hl7.fhir.core")) // while all packages are updated 741 return e.getValue().getAsString(); 742 } 743 } 744 } 745 if (npm.has("fhirVersions")) { 746 JsonElement e = npm.get("fhirVersions"); 747 if (e.isJsonArray() && e.getAsJsonArray().size() > 0) { 748 return npm.getAsJsonArray("fhirVersions").get(0).getAsString(); 749 } 750 } 751 if (dep != null) { 752 // legacy simplifier support: 753 if (dep.has("simplifier.core.r4")) 754 return "4.0"; 755 if (dep.has("simplifier.core.r3")) 756 return "3.0"; 757 if (dep.has("simplifier.core.r2")) 758 return "2.0"; 759 } 760 throw new FHIRException("no core dependency or FHIR Version found in the Package definition"); 761 } 762 } 763 764 public String summary() { 765 if (path != null) 766 return path; 767 else 768 return "memory"; 769 } 770 771 public boolean isType(PackageType template) { 772 return template.getCode().equals(type()); 773 } 774 775 public String type() { 776 return JSONUtil.str(npm, "type"); 777 } 778 779 public String description() { 780 return JSONUtil.str(npm, "description"); 781 } 782 783 public String getPath() { 784 return path; 785 } 786 787 public List<String> dependencies() { 788 List<String> res = new ArrayList<>(); 789 if (npm.has("dependencies")) { 790 for (Entry<String, JsonElement> e : npm.getAsJsonObject("dependencies").entrySet()) { 791 res.add(e.getKey()+"#"+e.getValue().getAsString()); 792 } 793 } 794 return res; 795 } 796 797 public String homepage() { 798 return JSONUtil.str(npm, "homepage"); 799 } 800 801 public String url() { 802 return JSONUtil.str(npm, "url"); 803 } 804 805 806 public String title() { 807 return JSONUtil.str(npm, "title"); 808 } 809 810 public String toolsVersion() { 811 return JSONUtil.str(npm, "tools-version"); 812 } 813 814 public String license() { 815 return JSONUtil.str(npm, "license"); 816 } 817 818 // /** 819 // * only for use by the package manager itself 820 // * 821 // * @param path 822 // */ 823 // public void setPath(String path) { 824 // this.path = path; 825 // } 826 827 public String getWebLocation() { 828 if (npm.has("url") && npm.get("url").isJsonPrimitive()) { 829 return PackageHacker.fixPackageUrl(npm.get("url").getAsString()); 830 } else { 831 return JSONUtil.str(npm, "canonical"); 832 } 833 } 834 835 public InputStream loadResource(String type, String id) throws IOException { 836 NpmPackageFolder f = folders.get("package"); 837 JsonArray files = f.index.getAsJsonArray("files"); 838 for (JsonElement e : files) { 839 JsonObject i = (JsonObject) e; 840 if (type.equals(JSONUtil.str(i, "resourceType")) && id.equals(JSONUtil.str(i, "id"))) { 841 return load("package", JSONUtil.str(i, "filename")); 842 } 843 } 844 return null; 845 } 846 847 public InputStream loadExampleResource(String type, String id) throws IOException { 848 NpmPackageFolder f = folders.get("example"); 849 if (f != null) { 850 JsonArray files = f.index.getAsJsonArray("files"); 851 for (JsonElement e : files) { 852 JsonObject i = (JsonObject) e; 853 if (type.equals(JSONUtil.str(i, "resourceType")) && id.equals(JSONUtil.str(i, "id"))) { 854 return load("example", JSONUtil.str(i, "filename")); 855 } 856 } 857 } 858 return null; 859 } 860 861 /** special case when playing around inside the package **/ 862 public Map<String, NpmPackageFolder> getFolders() { 863 return folders; 864 } 865 866 public void save(File directory) throws IOException { 867 File dir = new File(Utilities.path(directory.getAbsolutePath(), name())); 868 if (!dir.exists()) { 869 Utilities.createDirectory(dir.getAbsolutePath()); 870 } else { 871 Utilities.clearDirectory(dir.getAbsolutePath()); 872 } 873 874 for (NpmPackageFolder folder : folders.values()) { 875 String n = folder.name; 876 877 File pd = new File(Utilities.path(dir.getAbsolutePath(), n)); 878 if (!pd.exists()) { 879 Utilities.createDirectory(pd.getAbsolutePath()); 880 } 881 NpmPackageIndexBuilder indexer = new NpmPackageIndexBuilder(); 882 indexer.start(); 883 for (String s : folder.content.keySet()) { 884 byte[] b = folder.content.get(s); 885 indexer.seeFile(s, b); 886 if (!s.equals(".index.json") && !s.equals("package.json")) { 887 TextFile.bytesToFile(b, Utilities.path(dir.getAbsolutePath(), n, s)); 888 } 889 } 890 byte[] cnt = indexer.build().getBytes(StandardCharsets.UTF_8); 891 TextFile.bytesToFile(cnt, Utilities.path(dir.getAbsolutePath(), n, ".index.json")); 892 } 893 byte[] cnt = TextFile.stringToBytes(new GsonBuilder().setPrettyPrinting().create().toJson(npm), false); 894 TextFile.bytesToFile(cnt, Utilities.path(dir.getAbsolutePath(), "package", "package.json")); 895 } 896 897 public void save(OutputStream stream) throws IOException { 898 TarArchiveOutputStream tar; 899 ByteArrayOutputStream OutputStream; 900 BufferedOutputStream bufferedOutputStream; 901 GzipCompressorOutputStream gzipOutputStream; 902 903 OutputStream = new ByteArrayOutputStream(); 904 bufferedOutputStream = new BufferedOutputStream(OutputStream); 905 gzipOutputStream = new GzipCompressorOutputStream(bufferedOutputStream); 906 tar = new TarArchiveOutputStream(gzipOutputStream); 907 908 909 for (NpmPackageFolder folder : folders.values()) { 910 String n = folder.name; 911 if (!"package".equals(n) && !(n.startsWith("package/") || n.startsWith("package\\"))) { 912 n = "package/"+n; 913 } 914 NpmPackageIndexBuilder indexer = new NpmPackageIndexBuilder(); 915 indexer.start(); 916 for (String s : folder.content.keySet()) { 917 byte[] b = folder.content.get(s); 918 String name = n+"/"+s; 919 indexer.seeFile(s, b); 920 if (!s.equals(".index.json") && !s.equals("package.json")) { 921 TarArchiveEntry entry = new TarArchiveEntry(name); 922 entry.setSize(b.length); 923 tar.putArchiveEntry(entry); 924 tar.write(b); 925 tar.closeArchiveEntry(); 926 } 927 } 928 byte[] cnt = indexer.build().getBytes(StandardCharsets.UTF_8); 929 TarArchiveEntry entry = new TarArchiveEntry(n+"/.index.json"); 930 entry.setSize(cnt.length); 931 tar.putArchiveEntry(entry); 932 tar.write(cnt); 933 tar.closeArchiveEntry(); 934 } 935 byte[] cnt = TextFile.stringToBytes(new GsonBuilder().setPrettyPrinting().create().toJson(npm), false); 936 TarArchiveEntry entry = new TarArchiveEntry("package/package.json"); 937 entry.setSize(cnt.length); 938 tar.putArchiveEntry(entry); 939 tar.write(cnt); 940 tar.closeArchiveEntry(); 941 942 tar.finish(); 943 tar.close(); 944 gzipOutputStream.close(); 945 bufferedOutputStream.close(); 946 OutputStream.close(); 947 byte[] b = OutputStream.toByteArray(); 948 stream.write(b); 949 } 950 951 /** 952 * Keys are resource type names, values are filenames 953 */ 954 public Map<String, List<String>> getTypes() { 955 return folders.get("package").types; 956 } 957 958 public String fhirVersionList() { 959 if (npm.has("fhirVersions")) { 960 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 961 if (npm.get("fhirVersions").isJsonArray()) { 962 for (JsonElement n : npm.getAsJsonArray("fhirVersions")) { 963 b.append(n.getAsString()); 964 } 965 } 966 if (npm.get("fhirVersions").isJsonPrimitive()) { 967 b.append(npm.get("fhirVersions").getAsString()); 968 } 969 return b.toString(); 970 } else 971 return ""; 972 } 973 974 public String dependencySummary() { 975 if (npm.has("dependencies")) { 976 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 977 for (Entry<String, JsonElement> e : npm.getAsJsonObject("dependencies").entrySet()) { 978 b.append(e.getKey()+"#"+e.getValue().getAsString()); 979 } 980 return b.toString(); 981 } else 982 return ""; 983 } 984 985 public void unPack(String dir) throws IOException { 986 unPack (dir, false); 987 } 988 989 public void unPackWithAppend(String dir) throws IOException { 990 unPack (dir, true); 991 } 992 993 public void unPack(String dir, boolean withAppend) throws IOException { 994 for (NpmPackageFolder folder : folders.values()) { 995 String dn = folder.getName(); 996 if (!dn.equals("package") && (dn.startsWith("package/") || dn.startsWith("package\\"))) { 997 dn = dn.substring(8); 998 } 999 if (dn.equals("$root")) { 1000 dn = dir; 1001 } else { 1002 dn = Utilities.path(dir, dn); 1003 } 1004 Utilities.createDirectory(dn); 1005 for (String s : folder.listFiles()) { 1006 String fn = Utilities.path(dn, s); 1007 File f = new File(fn); 1008 if (withAppend && f.getName().startsWith("_append.")) { 1009 String appendFn = Utilities.path(dn, s.substring(8)); 1010 if (new File(appendFn).exists()) 1011 TextFile.appendBytesToFile(folder.fetchFile(s), appendFn); 1012 else 1013 TextFile.bytesToFile(folder.fetchFile(s), appendFn); 1014 } else 1015 TextFile.bytesToFile(folder.fetchFile(s), fn); 1016 } 1017// if (path != null) 1018// FileUtils.copyDirectory(new File(path), new File(dir)); 1019 } 1020 } 1021 1022 public void debugDump(String purpose) { 1023// System.out.println("Debug Dump of Package for '"+purpose+"'. Path = "+path); 1024// System.out.println(" npm = "+name()+"#"+version()+", canonical = "+canonical()); 1025// System.out.println(" folders = "+folders.size()); 1026// for (String s : sorted(folders.keySet())) { 1027// NpmPackageFolder folder = folders.get(s); 1028// System.out.println(" "+folder.dump()); 1029// } 1030 } 1031 1032 private List<String> sorted(Set<String> keys) { 1033 List<String> res = new ArrayList<String>(); 1034 res.addAll(keys); 1035 Collections.sort(res); 1036 return res ; 1037 } 1038 1039 public void clearFolder(String folderName) { 1040 NpmPackageFolder folder = folders.get(folderName); 1041 folder.content.clear(); 1042 folder.types.clear(); 1043 } 1044 1045 public void deleteFolder(String folderName) { 1046 folders.remove(folderName); 1047 } 1048 1049 public void addFile(String folderName, String name, byte[] cnt, String type) { 1050 if (!folders.containsKey(folderName)) { 1051 folders.put(folderName, new NpmPackageFolder(folderName)); 1052 } 1053 NpmPackageFolder folder = folders.get(folderName); 1054 folder.content.put(name, cnt); 1055 if (!folder.types.containsKey(type)) 1056 folder.types.put(type, new ArrayList<>()); 1057 folder.types.get(type).add(name); 1058 } 1059 1060 public void loadAllFiles() throws IOException { 1061 for (String folder : folders.keySet()) { 1062 NpmPackageFolder pf = folders.get(folder); 1063 String p = folder.contains("$") ? path : Utilities.path(path, folder); 1064 for (File f : new File(p).listFiles()) { 1065 if (!f.isDirectory() && !isInternalExemptFile(f)) { 1066 pf.getContent().put(f.getName(), TextFile.fileToBytes(f)); 1067 } 1068 } 1069 } 1070 } 1071 1072 public void loadAllFiles(ITransformingLoader loader) throws IOException { 1073 for (String folder : folders.keySet()) { 1074 NpmPackageFolder pf = folders.get(folder); 1075 String p = folder.contains("$") ? path : Utilities.path(path, folder); 1076 for (File f : new File(p).listFiles()) { 1077 if (!f.isDirectory() && !isInternalExemptFile(f)) { 1078 pf.getContent().put(f.getName(), loader.load(f)); 1079 } 1080 } 1081 } 1082 } 1083 1084 public boolean isChangedByLoader() { 1085 return changedByLoader; 1086 } 1087 1088 public boolean isCore() { 1089 return "fhir.core".equals(JSONUtil.str(npm, "type")); 1090 } 1091 1092 public boolean hasCanonical(String url) { 1093 if (url == null) { 1094 return false; 1095 } 1096 String u = url.contains("|") ? url.substring(0, url.indexOf("|")) : url; 1097 String v = url.contains("|") ? url.substring(url.indexOf("|")+1) : null; 1098 NpmPackageFolder folder = folders.get("package"); 1099 if (folder != null) { 1100 for (JsonElement e : folder.index.getAsJsonArray("files")) { 1101 JsonObject o = (JsonObject) e; 1102 if (u.equals(JSONUtil.str(o, "url"))) { 1103 if (v == null || v.equals(JSONUtil.str(o, "version"))) { 1104 return true; 1105 } 1106 } 1107 } 1108 } 1109 return false; 1110 } 1111 1112 public boolean canLazyLoad() throws IOException { 1113 for (NpmPackageFolder folder : folders.values()) { 1114 if (folder.folder == null) { 1115 return false; 1116 } 1117 } 1118 if (Utilities.existsInList(name(), "fhir.test.data.r2", "fhir.test.data.r3", "fhir.test.data.r4", "fhir.tx.support.r2", "fhir.tx.support.r3", "fhir.tx.support.r4", "us.nlm.vsac")) { 1119 return true; 1120 } 1121 if (JSONUtil.bool(npm, "lazy-load")) { 1122 return true; 1123 } 1124 if (!hasFile("other", "spec.internals")) { 1125 return false; 1126 } 1127 return true; 1128 } 1129 1130 public boolean isNotForPublication() { 1131 return JSONUtil.bool(npm, "notForPublication"); 1132 } 1133 1134 public InputStream load(PackageResourceInformation p) throws FileNotFoundException { 1135 return new FileInputStream(p.filename); 1136 } 1137 1138 1139}