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