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