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