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}