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}