
001package org.hl7.fhir.r5.terminologies.client; 002 003import java.io.FileOutputStream; 004import java.io.IOException; 005import java.net.MalformedURLException; 006import java.net.URL; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.Map; 010import java.util.Set; 011 012import lombok.Getter; 013import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 014import org.hl7.fhir.r5.formats.IParser; 015import org.hl7.fhir.r5.formats.JsonParser; 016import org.hl7.fhir.r5.model.*; 017import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesCodeSystemComponent; 018import org.hl7.fhir.r5.model.TerminologyCapabilities.TerminologyCapabilitiesExpansionParameterComponent; 019import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache; 020 021import org.hl7.fhir.utilities.ENoDump; 022import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 023import org.hl7.fhir.utilities.VersionUtilities; 024 025@MarkedToMoveToAdjunctPackage 026public class TerminologyClientContext { 027 public static final String MIN_TEST_VERSION = "1.6.0"; 028 public static final String TX_BATCH_VERSION = "1.7.8"; // actually, it's 17.7., but there was an error in the tx.fhir.org code around this 029 public static final String LATEST_VERSION = "1.7.8"; // dito 030 031 public enum TerminologyClientContextUseType { 032 expand, validate, readVS, readCS 033 } 034 public class TerminologyClientContextUseCount { 035 private int expands; 036 private int validates; 037 private int readVS; 038 private int readCS; 039 040 public int getReadVS() { 041 return readVS; 042 } 043 public void setReadVS(int readVS) { 044 this.readVS = readVS; 045 } 046 public int getReadCS() { 047 return readCS; 048 } 049 public void setReadCS(int readCS) { 050 this.readCS = readCS; 051 } 052 public int getExpands() { 053 return expands; 054 } 055 public void setExpands(int expands) { 056 this.expands = expands; 057 } 058 public int getValidates() { 059 return validates; 060 } 061 public void setValidates(int validates) { 062 this.validates = validates; 063 } 064 } 065 066 private static boolean allowNonConformantServers = false; 067 private static boolean canAllowNonConformantServers = false; 068 069 private static boolean canUseCacheId; 070 071 private ITerminologyClient client; 072 073 private CapabilityStatement capabilitiesStatement; 074 private TerminologyCapabilities txcaps; 075 @Getter 076 private final TerminologyCache txCache; 077 private String testVersion; 078 079 private Map<String, TerminologyClientContextUseCount> useCounts = new HashMap<>(); 080 private boolean isTxCaching; 081 private final Set<String> cached = new HashSet<>(); 082 private boolean master; 083 private String cacheId; 084 085 protected TerminologyClientContext(ITerminologyClient client, TerminologyCache txCache, String cacheId, boolean master) throws IOException { 086 super(); 087 this.client = client; 088 this.txCache = txCache; 089 this.cacheId = cacheId; 090 this.master = master; 091 initialize(); 092 } 093 094 public Map<String, TerminologyClientContextUseCount> getUseCounts() { 095 return useCounts; 096 } 097 098 public boolean isMaster() { 099 return master; 100 } 101 102 public ITerminologyClient getClient() { 103 return client; 104 } 105 106 public void seeUse(Set<String> systems, TerminologyClientContextUseType useType) { 107 if (systems != null) { 108 for (String s : systems) { 109 seeUse(s, useType); 110 } 111 } 112 } 113 114 public void seeUse(String s, TerminologyClientContextUseType useType) { 115 TerminologyClientContextUseCount uc = useCounts.get(s); 116 if (uc == null) { 117 uc = new TerminologyClientContextUseCount(); 118 useCounts.put(s,uc); 119 } 120 switch (useType) { 121 case expand: 122 uc.expands++; 123 break; 124 case readVS: 125 uc.readVS++; 126 break; 127 case readCS: 128 uc.readCS++; 129 break; 130 case validate: 131 uc.validates++; 132 break; 133 default: 134 break; 135 } 136 } 137 138 public TerminologyCapabilities getTxCapabilities() { 139 return txcaps; 140 } 141 142 public void setTxCapabilities(TerminologyCapabilities txcaps) { 143 this.txcaps = txcaps; 144 try { 145 new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/temp/txcaps.json"), txcaps); 146 } catch (IOException e) { 147 148 } 149 } 150 151 public Set<String> getCached() { 152 return cached; 153 } 154 155 public boolean alreadyCached(CanonicalResource cr) { 156 return cached.contains(cr.getVUrl()); 157 } 158 159 public void addToCache(CanonicalResource cr) { 160 cached.add(cr.getVUrl()); 161 } 162 163 public String getAddress() { 164 return client.getAddress(); 165 } 166 167 public int getUseCount() { 168 return getClient().getUseCount(); 169 } 170 171 public boolean isTxCaching() { 172 return isTxCaching; 173 } 174 175 public void setTxCaching(boolean isTxCaching) { 176 this.isTxCaching = isTxCaching; 177 } 178 179 public boolean usingCache() { 180 return isTxCaching && cacheId != null; 181 } 182 183 public String getCacheId() { 184 return cacheId; 185 } 186 187 private void initialize() throws IOException { 188 189 // we don't cache the quick CS - we want to know that the server is with us. 190 capabilitiesStatement = client.getCapabilitiesStatement(); 191 checkFeature(); 192 if (txCache != null && txCache.hasTerminologyCapabilities(getAddress())) { 193 txcaps = txCache.getTerminologyCapabilities(getAddress()); 194 try { 195 new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/temp/txcaps.json"), txcaps); 196 } catch (IOException e) { 197 198 } 199 if (txcaps.getSoftware().hasVersion() && !txcaps.getSoftware().getVersion().equals(capabilitiesStatement.getSoftware().getVersion())) { 200 txcaps = null; 201 } 202 } 203 if (txcaps == null) { 204 txcaps = client.getTerminologyCapabilities(); 205 if (txCache != null) { 206 try { 207 txCache.cacheTerminologyCapabilities(getAddress(), txcaps); 208 } catch (IOException e) { 209 e.printStackTrace(); 210 } 211 } 212 } 213 if (txcaps != null && TerminologyClientContext.canUseCacheId) { 214 for (TerminologyCapabilitiesExpansionParameterComponent t : txcaps.getExpansion().getParameter()) { 215 if ("cache-id".equals(t.getName())) { 216 setTxCaching(true); 217 break; 218 } 219 } 220 } 221 222 } 223 224 private void checkFeature() { 225 if (!allowNonConformantServers && capabilitiesStatement != null) { 226 boolean csParams = false; 227 228 for (Extension t : capabilitiesStatement.getExtension()) { 229 if (ExtensionDefinitions.EXT_FEATURE.equals(t.getUrl())) { 230 String defn = t.getExtensionString("definition"); 231 if (ExtensionDefinitions.FEATURE_TX_TEST_VERSION.equals(defn)) { 232 testVersion = t.getExtensionString("value"); 233 } else if (ExtensionDefinitions.FEATURE_TX_CS_PARAMS.equals(defn)) { 234 csParams = "true".equals(t.getExtensionString("value")); 235 } 236 } 237 } 238 239 if (testVersion == null) { 240 if (canAllowNonConformantServers) { 241 throw new ENoDump("The terminology server "+client.getAddress()+" is not approved for use with this software (it does not pass the required tests).\r\nIf you wish to use this server, add the parameter -authorise-non-conformant-tx-servers to the command line parameters"); 242 } else { 243 throw new ENoDump("The terminology server "+client.getAddress()+" is not approved for use with this software (it does not pass the required tests)"); 244 } 245 } else if (!VersionUtilities.isThisOrLater(MIN_TEST_VERSION, testVersion, VersionUtilities.VersionPrecision.MINOR)) { 246 if (canAllowNonConformantServers) { 247 throw new ENoDump("The terminology server "+client.getAddress()+" is not approved for use with this software as it is too old (test version = "+testVersion+").\r\nIf you wish to use this server, add the parameter -authorise-non-conformant-tx-servers to the command line parameters"); 248 } else { 249 throw new ENoDump("The terminology server "+client.getAddress()+" is not approved for use with this software as it is too old (test version = "+testVersion+")"); 250 } 251 } else if (!csParams) { 252 if (canAllowNonConformantServers) { 253 throw new ENoDump("The terminology server "+client.getAddress()+" is not approved for use as it does not accept code systems in the tx-resource parameter.\r\nIf you wish to use this server, add the parameter -authorise-non-conformant-tx-servers to the command line parameters"); 254 } else { 255 throw new ENoDump("The terminology server "+client.getAddress()+" is not approved for use as it does not accept code systems in the tx-resource parameter"); 256 } 257 } else { 258 // all good 259 } 260 } 261 } 262 263 public boolean supportsSystem(String system) throws IOException { 264 try { 265 new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/temp/txcaps.json"), txcaps); 266 } catch (IOException e) { 267 268 } 269 for (TerminologyCapabilitiesCodeSystemComponent tccs : txcaps.getCodeSystem()) { 270 if (system.equals(tccs.getUri()) || (tccs.hasVersion() && system.equals(CanonicalType.urlWithVersion(tccs.getUri(), tccs.getVersionFirstRep().getCode())))) { 271 return true; 272 } 273 if (system.startsWith(tccs.getUri()+"|")) { 274 if (tccs.hasVersion()) { 275 for (TerminologyCapabilities.TerminologyCapabilitiesCodeSystemVersionComponent v : tccs.getVersion()) { 276 if (system.equals(CanonicalType.urlWithVersion(tccs.getUri(), v.getCode()))) { 277 return true; 278 } 279 } 280 } 281 } 282 } 283 return false; 284 } 285 286 public String getTxTestVersion() { 287 try { 288 return testVersion; 289 } catch (Exception e) { 290 // debug? 291 return null; 292 } 293 } 294 295 @Override 296 public String toString() { 297 return client.getAddress(); 298 } 299 300 public static boolean isCanUseCacheId() { 301 return canUseCacheId; 302 } 303 304 public static void setCanUseCacheId(boolean canUseCacheId) { 305 TerminologyClientContext.canUseCacheId = canUseCacheId; 306 } 307 308 public String getHost() { 309 try { 310 URL uri = new URL(getAddress()); 311 return uri.getHost(); 312 } catch (MalformedURLException e) { 313 return getAddress(); 314 } 315 } 316 317 public static boolean isAllowNonConformantServers() { 318 return allowNonConformantServers; 319 } 320 321 public static void setAllowNonConformantServers(boolean allowNonConformantServers) { 322 TerminologyClientContext.allowNonConformantServers = allowNonConformantServers; 323 } 324 325 public static boolean isCanAllowNonConformantServers() { 326 return canAllowNonConformantServers; 327 } 328 329 public static void setCanAllowNonConformantServers(boolean canAllowNonConformantServers) { 330 TerminologyClientContext.canAllowNonConformantServers = canAllowNonConformantServers; 331 } 332 333}