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}