001package org.hl7.fhir.utilities.npm; 002 003import java.io.ByteArrayInputStream; 004import java.io.FileNotFoundException; 005import java.io.IOException; 006import java.io.InputStream; 007import java.time.Instant; 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.Date; 011import java.util.List; 012import java.util.Set; 013 014import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 015import org.hl7.fhir.utilities.SimpleHTTPClient; 016import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; 017import org.hl7.fhir.utilities.TextFile; 018import org.hl7.fhir.utilities.Utilities; 019import org.hl7.fhir.utilities.VersionUtilities; 020import org.hl7.fhir.utilities.json.model.JsonArray; 021import org.hl7.fhir.utilities.json.model.JsonObject; 022import org.hl7.fhir.utilities.json.model.JsonProperty; 023import org.hl7.fhir.utilities.json.parser.JsonParser; 024 025public class PackageClient { 026 027 028 029 public static final String PRIMARY_SERVER = "http://packages.fhir.org"; 030 public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages"; 031 032 private String address; 033 private String cacheFolder; 034 035 036 public PackageClient(String address) { 037 super(); 038 this.address = address; 039 try { 040 cacheFolder = Utilities.path(System.getProperty("user.home"), ".fhir", "package-client"); 041 Utilities.createDirectory(cacheFolder); 042 } catch (IOException e) { 043 } 044 } 045 046 public boolean exists(String id, String ver) throws IOException { 047 List<PackageInfo> vl = getVersions(id); 048 for (PackageInfo pi : vl) { 049 if (ver == null || ver.equals(pi.getVersion())) { 050 return true; 051 } 052 } 053 return false; 054 } 055 056 public InputStream fetch(String id, String ver) throws IOException { 057 return fetchCached(Utilities.pathURL(address, id, ver)); 058 } 059 060 public InputStream fetch(PackageInfo info) throws IOException { 061 return fetchCached(Utilities.pathURL(address, info.getId(), info.getVersion())); 062 } 063 064 public InputStream fetchNpm(String id, String ver) throws IOException { 065 return fetchCached(Utilities.pathURL(address, id, "-", id+"-"+ver+".tgz")); 066 } 067 068 public InputStream fetchCached(String url) throws IOException, FileNotFoundException { 069 return fetchUrl(url, null); 070 } 071 072 protected String fn(String url) { 073 String[] p = url.split("\\/"); 074 return p[2]+"-"+p[p.length-2]+"-"+p[p.length-1]+".tgz"; 075 } 076 077 public List<PackageInfo> getVersions(String id) throws IOException { 078 String url = Utilities.pathURL(address, id); 079 List<PackageInfo> res = new ArrayList<>(); 080 JsonObject json; 081 try { 082 json = fetchJson(url); 083 JsonObject versions = json.getJsonObject("versions"); 084 boolean hasDates = true; 085 if (versions != null) { 086 for (JsonProperty v : versions.getProperties()) { 087 JsonObject obj = versions.getJsonObject(v.getName()); 088 Instant d = obj.hasString("date") ? obj.asDate("date") : null; 089 if (d == null) { 090 hasDates = false; 091 } 092 res.add(new PackageInfo(obj.asString("Name", "name"), 093 obj.asString("Version", "version"), 094 obj.asString("FhirVersion", "fhirVersion"), 095 obj.asString("Description", "description"), 096 obj.asString("url"), 097 obj.asString("canonical"), 098 address, d)); 099 } 100 } 101 if (hasDates) { 102 Collections.sort(res, new PackageInfo.PackageInfoSorter(true)); 103 } else { 104 Collections.sort(res, new PackageInfo.PackageInfoSorter(false)); 105 } 106 } catch (Exception e) { 107 System.out.println("Error fetching "+url+": "+e.getMessage()); 108 } 109 return res; 110 } 111 112 public List<PackageInfo> search(String name, String canonical, String fhirVersion, boolean preRelease) throws IOException { 113 CommaSeparatedStringBuilder params = new CommaSeparatedStringBuilder("&"); 114 if (!Utilities.noString(name)) { 115 params.append("name="+name); 116 } 117 if (!Utilities.noString(canonical)) { 118 params.append("pkgcanonical="+canonical); 119 } 120 if (!Utilities.noString(fhirVersion)) { 121 params.append("fhirversion="+fhirVersion); 122 } 123 if (preRelease) { 124 params.append("prerelease="+preRelease); 125 } 126 List<PackageInfo> res = new ArrayList<>(); 127 try { 128 JsonArray json = fetchJsonArray(Utilities.pathURL(address, "catalog?")+params.toString()); 129 boolean hasDates = true; 130 for (JsonObject obj : json.asJsonObjects()) { 131 Instant d = obj.has("date") ? obj.asDate("date") : null; 132 if (d == null) { 133 hasDates = false; 134 } 135 res.add(new PackageInfo(obj.asString("Name", "name"), 136 obj.asString("Version", "version"), 137 obj.asString("FhirVersion", "fhirVersion"), 138 obj.asString("Description", "description"), 139 obj.asString("url"), 140 obj.asString("canonical"), 141 address, d)); 142 } 143 if (hasDates) { 144 Collections.sort(res, new PackageInfo.PackageInfoSorter(true)); 145 } else { 146 Collections.sort(res, new PackageInfo.PackageInfoSorter(false)); 147 } 148 } catch (IOException e1) { 149 } 150 return res; 151 } 152 153 public Date getNewPackages(Date lastCalled, List<PackageInfo> updates) { 154 return null; 155 } 156 157 private InputStream fetchUrl(String source, String accept) throws IOException { 158 SimpleHTTPClient http = new SimpleHTTPClient(); 159 HTTPResult res = http.get(source, accept); 160 res.checkThrowException(); 161 return new ByteArrayInputStream(res.getContent()); 162 } 163 164 private JsonObject fetchJson(String source) throws IOException { 165 String src = TextFile.streamToString(fetchUrl(source, "application/json")); 166 //System.out.println(src); 167 return JsonParser.parseObject(src); 168 } 169 170 private JsonArray fetchJsonArray(String source) throws IOException { 171 String src = TextFile.streamToString(fetchUrl(source, "application/json")); 172 //System.out.println(src); 173 return (JsonArray) JsonParser.parse(src); 174 } 175 176 public String url(String id, String v) { 177 return Utilities.pathURL(address, id, v); 178 } 179 180 public String getLatestVersion(String id) throws IOException { 181 List<PackageInfo> list = getVersions(id); 182 if (list.isEmpty()) { 183 throw new IOException("Package not found: "+id); 184 } else { 185 String v = list.get(0).getVersion(); 186 for (PackageInfo p : list) { 187 if (VersionUtilities.isThisOrLater(v, p.getVersion())) { 188 v = p.getVersion(); 189 } 190 } 191 return v; 192 } 193 } 194 195 public String getLatestVersion(String id, String specVersion) throws IOException { 196 List<PackageInfo> list = getVersions(id); 197 if (list.isEmpty()) { 198 throw new IOException("Package not found: "+id); 199 } else { 200 String v = null; 201 for (PackageInfo p : list) { 202 if (VersionUtilities.isMajMinOrLaterPatch(specVersion, p.getVersion())) { 203 v = p.getVersion(); 204 } 205 } 206 return v; 207 } 208 } 209 210 protected PackageInfo getPackageInfoFromJSON(JsonObject o, String name, String canonical, String fhirVersion) { 211 String id = o.asString("npm-name"); 212 String pname = o.asString("name"); 213 String pcanonical = o.asString("canonical"); 214 String description = o.asString("description"); 215 Instant d = o.has("date") ? o.asDate("date") : null; 216 boolean ok = true; 217 if (ok && !Utilities.noString(name)) { 218 ok = (pname != null && pname.contains(name)) || (description != null && description.contains(name)) || (id != null && id.contains(name)); 219 } 220 if (ok && !Utilities.noString(canonical)) { 221 ok = pcanonical.contains(canonical); 222 } 223 String version = null; 224 String fVersion = null; 225 String url = null; 226 227 if (ok) { 228 // if we can find something... 229 for (JsonObject e : o.getJsonObjects("editions")) { 230 if (fhirVersion == null || fhirVersion.equals(e.asString("fhir-version"))) { 231 String v = e.asString("ig-version"); 232 if (version == null || VersionUtilities.isThisOrLater(version, v)) { 233 version = v; 234 fVersion = e.getJsonArray("fhir-version").get(0).asString(); 235 url = e.asString("url"); 236 237 String npmPackage = e.asString("package"); 238 if (npmPackage != null && id == null) { 239 id = npmPackage.substring(0, npmPackage.indexOf("#")); 240 } 241 } 242 } 243 } 244 } 245 return new PackageInfo(id, version, fVersion, description, url, pcanonical, address, d); 246 } 247 248 public List<PackageInfo> listFromRegistry(String name, String canonical, String fhirVersion) throws IOException { 249 List<PackageInfo> result = new ArrayList<>(); 250 JsonObject packages = JsonParser.parseObjectFromUrl("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json?nocache=" + System.currentTimeMillis()); 251 for (JsonObject o : packages.getJsonObjects("guides")) { 252 if (o.has("canonical")) { 253 final PackageInfo packageInfo = getPackageInfoFromJSON(o, name, canonical, fhirVersion); 254 if (packageInfo.getVersion() != null) { 255 result.add(packageInfo); 256 } 257 } 258 } 259 return result; 260 } 261 262 public void findDependents(Set<String> list, String id) { 263 CommaSeparatedStringBuilder params = new CommaSeparatedStringBuilder("&"); 264 params.append("dependency="+id.replace("#", "|")); 265 try { 266 JsonArray json = fetchJsonArray(Utilities.pathURL(address, "catalog?")+params.toString()); 267 for (JsonObject obj : json.asJsonObjects()) { 268 list.add(obj.asString("Name", "name")); 269 } 270 } catch (IOException e1) { 271 } 272 } 273 274}