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}