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