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