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}