001package org.hl7.fhir.utilities.npm;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.util.ArrayList;
006import java.util.List;
007import java.util.function.Function;
008
009import javax.annotation.Nonnull;
010import javax.annotation.Nullable;
011
012import org.apache.commons.lang3.Validate;
013import org.hl7.fhir.exceptions.FHIRException;
014import org.hl7.fhir.utilities.Utilities;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018public abstract class BasePackageCacheManager implements IPackageCacheManager {
019
020  private static final Logger ourLog = LoggerFactory.getLogger(BasePackageCacheManager.class);
021  private List<String> myPackageServers = new ArrayList<>();
022  private Function<String, PackageClient> myClientFactory = address -> new PackageClient(address);
023  protected boolean silent;
024
025  /**
026   * Constructor
027   */
028  public BasePackageCacheManager() {
029    super();
030  }
031
032  /**
033   * Provide a new client factory implementation
034   */
035  public void setClientFactory(Function<String, PackageClient> theClientFactory) {
036    Validate.notNull(theClientFactory, "theClientFactory must not be null");
037    myClientFactory = theClientFactory;
038  }
039
040  public List<String> getPackageServers() {
041    return myPackageServers;
042  }
043
044  /**
045   * Add a package server that can be used to fetch remote packages
046   */
047  public void addPackageServer(@Nonnull String thePackageServer) {
048    Validate.notBlank(thePackageServer, "thePackageServer must not be null or empty");
049    if (!myPackageServers.contains(thePackageServer)) {
050      myPackageServers.add(thePackageServer);
051    }
052  }
053
054
055  /**
056   * Load the latest version of the identified package from the cache - it it exists
057   */
058  public NpmPackage loadPackageFromCacheOnly(String id) throws IOException {
059    return loadPackageFromCacheOnly(id, null);
060  }
061
062  /**
063   * Try to load a package from all registered package servers, and return <code>null</code>
064   * if it can not be found at any of them.
065   */
066  @Nullable
067  protected InputStreamWithSrc loadFromPackageServer(String id, String version) {
068
069    for (String nextPackageServer : getPackageServers()) {
070      if (okToUsePackageServer(nextPackageServer, id)) {
071        PackageClient packageClient = myClientFactory.apply(nextPackageServer);
072        try {
073          if (Utilities.noString(version)) {
074            version = packageClient.getLatestVersion(id);
075          }
076          if (version.endsWith(".x")) {
077            version = packageClient.getLatestVersion(id, version);
078          }
079          InputStream stream = packageClient.fetch(id, version);
080          String url = packageClient.url(id, version);
081          return new InputStreamWithSrc(stream, url, version);
082        } catch (IOException e) {
083          if (!silent) {
084            ourLog.info("Failed to resolve package {}#{} from server: {} ({})", id, version, nextPackageServer, e.getMessage());
085          }
086        }
087      }
088    }
089
090    return null;
091  }
092
093  // hack - we have a hacked 1.4.0 out there. Only packages2.fhir.org has it.
094  // this is not a long term thing, but it's not clear how to release patches for 
095  // 1.4.0
096  private boolean okToUsePackageServer(String server, String id) {
097    if ("http://packages.fhir.org".equals(server) && "hl7.fhir.r2b.core".equals(id)) {
098      return false;
099    }
100    return true;
101  }
102
103  public abstract NpmPackage loadPackageFromCacheOnly(String id, @Nullable String version) throws IOException;
104
105  @Override
106  public String getPackageUrl(String packageId) throws IOException {
107    String result = null;
108    NpmPackage npm = loadPackageFromCacheOnly(packageId);
109    if (npm != null) {
110      return npm.canonical();
111    }
112
113    for (String nextPackageServer : getPackageServers()) {
114      result = getPackageUrl(packageId, nextPackageServer);
115      if (result != null) {
116        return result;
117      }
118    }
119
120    return result;
121  }
122
123
124  private String getPackageUrl(String packageId, String server) throws IOException {
125    PackageClient pc = myClientFactory.apply(server);
126    List<PackageInfo> res = pc.search(packageId, null, null, false);
127    if (res.size() == 0) {
128      return null;
129    } else {
130      return res.get(0).getUrl();
131    }
132  }
133
134
135  @Override
136  public String getPackageId(String canonicalUrl) throws IOException {
137    String result = null;
138
139    for (String nextPackageServer : getPackageServers()) {
140      result = getPackageId(canonicalUrl, nextPackageServer);
141      if (result != null) {
142        break;
143      }
144    }
145
146    return result;
147  }
148
149  private String getPackageId(String canonical, String server) throws IOException {
150    if (canonical == null) {
151      return null;
152    }
153    PackageClient pc = myClientFactory.apply(server);
154    List<PackageInfo> res = pc.search(null, canonical, null, false);
155    if (res.size() == 0) {
156      return null;
157    } else {
158      // this is driven by HL7 Australia (http://hl7.org.au/fhir/ is the canonical url for the base package, and the root for all the others)
159      for (PackageInfo pi : res) {
160        if (canonical.equals(pi.getCanonical())) {
161          return pi.getId();
162        }
163      }
164      return res.get(0).getId();
165    }
166  }
167
168  public class InputStreamWithSrc {
169
170    public InputStream stream;
171    public String url;
172    public String version;
173
174    public InputStreamWithSrc(InputStream stream, String url, String version) {
175      this.stream = stream;
176      this.url = url;
177      this.version = version;
178    }
179  }
180
181  public NpmPackage loadPackage(String idAndVer) throws FHIRException, IOException {
182    return loadPackage(idAndVer, null);
183  }
184
185
186  public void setSilent(boolean silent) {
187    this.silent = silent;    
188  }
189
190}