001package org.hl7.fhir.utilities.cache; 002 003import java.io.BufferedOutputStream; 004import java.io.ByteArrayInputStream; 005import java.io.ByteArrayOutputStream; 006import java.io.File; 007import java.io.FileInputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.HashMap; 013import java.util.List; 014import java.util.Map; 015import java.util.Map.Entry; 016import java.util.zip.ZipEntry; 017import java.util.zip.ZipInputStream; 018 019import org.apache.commons.io.FileUtils; 020import org.hl7.fhir.utilities.IniFile; 021import org.hl7.fhir.utilities.TextFile; 022import org.hl7.fhir.utilities.Utilities; 023import org.hl7.fhir.utilities.cache.PackageGenerator.PackageType; 024 025import com.google.gson.JsonObject; 026 027/** 028 * info and loader for a package 029 * 030 * Packages may exist on disk in the cache, or purely in memory when they are loaded on the fly 031 * 032 * Packages are contained in subfolders (see the package spec). The FHIR resources will be in "package" 033 * 034 * @author Grahame Grieve 035 * 036 */ 037 public class NpmPackage { 038 039 private String path; 040 private List<String> folders = new ArrayList<String>(); 041 private Map<String, byte[]> content = new HashMap<String, byte[]>(); 042 private JsonObject npm; 043 private IniFile cache; 044 045 public NpmPackage(JsonObject npm, Map<String, byte[]> content, List<String> folders) { 046 this.path = null; 047 this.content = content; 048 this.npm = npm; 049 this.folders = folders; 050 } 051 052 public NpmPackage(String path) throws IOException { 053 this.path = path; 054 if (path != null) { 055 for (String f : sorted(new File(path).list())) { 056 if (new File(Utilities.path(path, f)).isDirectory()) { 057 folders.add(f); 058 } 059 } 060 npm = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(Utilities.path(path, "package", "package.json"))); 061 cache = new IniFile(Utilities.path(path, "cache.ini")); 062 } 063 } 064 065 private List<String> sorted(String[] keys) { 066 List<String> names = new ArrayList<String>(); 067 for (String s : keys) 068 names.add(s); 069 Collections.sort(names); 070 return names; 071 } 072 073 public static NpmPackage fromZip(InputStream stream, boolean dropRootFolder) throws IOException { 074 NpmPackage res = new NpmPackage(null); 075 ZipInputStream zip = new ZipInputStream(stream); 076 ZipEntry ze; 077 while ((ze = zip.getNextEntry()) != null) { 078 int size; 079 byte[] buffer = new byte[2048]; 080 081 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 082 BufferedOutputStream bos = new BufferedOutputStream(bytes, buffer.length); 083 084 while ((size = zip.read(buffer, 0, buffer.length)) != -1) { 085 bos.write(buffer, 0, size); 086 } 087 bos.flush(); 088 bos.close(); 089 if (bytes.size() > 0) 090 if (dropRootFolder) 091 res.content.put(ze.getName().substring(ze.getName().indexOf("/")+1), bytes.toByteArray()); 092 else 093 res.content.put(ze.getName(), bytes.toByteArray()); 094 095 zip.closeEntry(); 096 } 097 zip.close(); 098 res.npm = (JsonObject) new com.google.gson.JsonParser().parse(new String(res.content.get("package/package.json"))); 099 return res; 100 } 101 102 103 /** 104 * Accessing the contents of the package - get a list of files in a subfolder of the package 105 * 106 * @param folder 107 * @return 108 * @throws IOException 109 */ 110 public List<String> list(String folder) throws IOException { 111 List<String> res = new ArrayList<String>(); 112 if (path != null) { 113 File f = new File(Utilities.path(path, folder)); 114 if (f.exists() && f.isDirectory()) 115 for (String s : f.list()) 116 res.add(s); 117 } else { 118 for (String s : content.keySet()) { 119 if (s.startsWith(folder+"/") && !s.substring(folder.length()+2).contains("/")) 120 res.add(s.substring(folder.length()+1)); 121 } 122 } 123 return res; 124 } 125 126 /** 127 * Copies all the files in the package folder [folder] to the nominated dest, 128 * and returns a list of all the file names copied 129 * 130 * @param folder 131 * @return 132 * @throws IOException 133 */ 134 public List<String> copyTo(String folder, String dest) throws IOException { 135 List<String> res = new ArrayList<String>(); 136 if (path != null) { 137 copyToDest(Utilities.path(path, folder), Utilities.path(path, folder), dest, res); 138 } else { 139 for (Entry<String, byte[]> e : content.entrySet()) { 140 if (e.getKey().startsWith(folder+"/")) { 141 String s = e.getKey().substring(folder.length()+1); 142 res.add(s); 143 String dst = Utilities.path(dest, s); 144 String dstDir = Utilities.getDirectoryForFile(dst); 145 Utilities.createDirectory(dstDir); 146 TextFile.bytesToFile(e.getValue(), dst); 147 } 148 } 149 } 150 return res; 151 } 152 153 private void copyToDest(String base, String folder, String dest, List<String> res) throws IOException { 154 for (File f : new File(folder).listFiles()) { 155 if (f.isDirectory()) { 156 copyToDest(base, f.getAbsolutePath(), Utilities.path(dest, f.getName()), res); 157 } else { 158 String dst = Utilities.path(dest, f.getName()); 159 FileUtils.copyFile(f, new File(dst), true); 160 res.add(f.getAbsolutePath().substring(base.length()+1)); 161 } 162 } 163 } 164 165 /** 166 * get a stream that contains the contents of one of the files in a folder 167 * 168 * @param folder 169 * @param file 170 * @return 171 * @throws IOException 172 */ 173 public InputStream load(String folder, String file) throws IOException { 174 if (content.containsKey(folder+"/"+file)) 175 return new ByteArrayInputStream(content.get(folder+"/"+file)); 176 else { 177 File f = new File(Utilities.path(path, folder, file)); 178 if (f.exists()) 179 return new FileInputStream(f); 180 throw new IOException("Unable to find the file "+folder+"/"+file+" in the package "+name()); 181 } 182 } 183 184 /** 185 * Handle to the package json file 186 * 187 * @return 188 */ 189 public JsonObject getNpm() { 190 return npm; 191 } 192 193 /** 194 * convenience method for getting the package name 195 * @return 196 */ 197 public String name() { 198 return npm.get("name").getAsString(); 199 } 200 201 public String canonical() { 202 return npm.get("canonical").getAsString(); 203 } 204 205 /** 206 * convenience method for getting the package version 207 * @return 208 */ 209 public String version() { 210 return npm.get("version").getAsString(); 211 } 212 213 /** 214 * convenience method for getting the package fhir version 215 * @return 216 */ 217 public String fhirVersion() { 218 if ("hl7.fhir.core".equals(npm.get("name").getAsString())) 219 return npm.get("version").getAsString(); 220 else 221 return npm.getAsJsonObject("dependencies").get("hl7.fhir.core").getAsString(); 222 } 223 224 public String description() { 225 if (path != null) 226 return path; 227 else 228 return "memory"; 229 } 230 231 public boolean isType(PackageType template) { 232 return template.getCode().equals(type()); 233 } 234 235 public String type() { 236 return npm.get("type").getAsString(); 237 } 238 239 public String getPath() { 240 return path; 241 } 242 243 public IniFile getCache() { 244 return cache; 245 } 246 247 /** 248 * only for use by the package manager itself 249 * 250 * @param path 251 */ 252 public void setPath(String path) { 253 this.path = path; 254 } 255 256 public String getWebLocation() { 257 if (npm.has("url")) 258 return npm.get("url").getAsString(); 259 else 260 return npm.get("canonical").getAsString(); 261 } 262 263 264 }