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}