001package org.hl7.fhir.convertors.misc;
002
003import java.io.File;
004import java.io.FileInputStream;
005import java.io.FileNotFoundException;
006import java.io.FileOutputStream;
007import java.io.IOException;
008import java.net.URISyntaxException;
009import java.text.ParseException;
010import java.time.Duration;
011import java.util.ArrayList;
012import java.util.Collections;
013import java.util.HashMap;
014import java.util.List;
015import java.util.Map;
016
017import org.hl7.fhir.exceptions.FHIRException;
018import org.hl7.fhir.r4.formats.IParser.OutputStyle;
019import org.hl7.fhir.r4.formats.JsonParser;
020import org.hl7.fhir.r4.model.CapabilityStatement;
021import org.hl7.fhir.r4.model.IntegerType;
022import org.hl7.fhir.r4.model.OperationOutcome;
023import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
024import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
025import org.hl7.fhir.r4.model.Parameters;
026import org.hl7.fhir.r4.model.UriType;
027import org.hl7.fhir.r4.model.ValueSet;
028import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
029import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
030import org.hl7.fhir.r4.terminologies.JurisdictionUtilities;
031import org.hl7.fhir.utilities.CSVReader;
032import org.hl7.fhir.utilities.Utilities;
033import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
034import org.hl7.fhir.utilities.json.model.JsonArray;
035import org.hl7.fhir.utilities.json.model.JsonObject;
036import org.hl7.fhir.utilities.json.model.JsonProperty;
037
038public class VSACImporter extends OIDBasedValueSetImporter {
039  
040  public VSACImporter() throws FHIRException, IOException {
041    super();
042    init();
043  }
044
045  public static void main(String[] args) throws FHIRException, IOException, ParseException, URISyntaxException {
046    VSACImporter self = new VSACImporter();
047    self.process(args[0], args[1], args[2], "true".equals(args[3]));
048  }
049
050  private void process(String source, String dest, String apiKey, boolean onlyNew) throws FHIRException, IOException, URISyntaxException {
051    CSVReader csv = new CSVReader(ManagedFileAccess.inStream(source));
052    csv.readHeaders();
053    Map<String, String> errs = new HashMap<>();
054
055    FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", "fhir/vsac");
056    fhirToolingClient.setUsername("apikey");
057    fhirToolingClient.setPassword(apiKey);
058    fhirToolingClient.setTimeoutNormal(30000);
059    fhirToolingClient.setTimeoutExpand(30000);
060
061    CapabilityStatement cs = fhirToolingClient.getCapabilitiesStatement();
062    JsonParser json = new JsonParser();
063    json.setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "vsac-capability-statmenet.json")), cs);
064
065    System.out.println("Loading");
066    List<String> oids = new ArrayList<>();
067    while (csv.line()) {
068      String oid = csv.cell("OID");
069      if (!onlyNew || !(ManagedFileAccess.file(Utilities.path(dest, "ValueSet-" + oid + ".json")).exists())) {
070        oids.add(oid);
071      }
072    }
073    Collections.sort(oids);
074    System.out.println("Go: "+oids.size()+" oids");
075    int i = 0;
076    int j = 0;
077    long t = System.currentTimeMillis();
078    long tt = System.currentTimeMillis();
079    for (String oid : oids) {
080      try {
081        long t3 = System.currentTimeMillis();
082        if (processOid(dest, onlyNew, errs, fhirToolingClient, oid.trim())) {
083          j++;
084        }
085        i++;
086        System.out.print(":"+((System.currentTimeMillis() - t3) / 1000));
087        if (i % 100 == 0) {
088          long elapsed = System.currentTimeMillis() - t;
089          System.out.println("");
090          System.out.println(i+": "+j+" ("+((j * 100) / i)+"%) @ "+Utilities.describeDuration(elapsed)
091              +", "+(elapsed/100000)+"sec/vs, estimated "+Utilities.describeDuration(estimate(i, oids.size(), tt))+" remaining");
092          t = System.currentTimeMillis();
093        }
094      } catch (Exception e) {
095        e.printStackTrace();
096        System.out.println("Unable to fetch OID " + oid + ": " + e.getMessage());
097        errs.put(oid, e.getMessage());
098      }
099    }
100    OperationOutcome oo = new OperationOutcome();
101    for (String oid : errs.keySet()) {
102      oo.addIssue().setSeverity(IssueSeverity.ERROR).setCode(IssueType.EXCEPTION).setDiagnostics(errs.get(oid)).addLocation(oid);
103    }
104    new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "other", "OperationOutcome-vsac-errors.json")), oo);
105    System.out.println("Done. " + i + " ValueSets in "+Utilities.describeDuration(System.currentTimeMillis() - tt));
106  }
107
108  private long estimate(int i, int size, long tt) {
109    long elapsed = System.currentTimeMillis() - tt;
110    long average = elapsed / i;
111    return (size - i) * average;
112  }
113
114  private boolean processOid(String dest, boolean onlyNew, Map<String, String> errs, FHIRToolingClient fhirToolingClient, String oid)
115      throws IOException, InterruptedException, FileNotFoundException {
116    
117    while (true) {
118      boolean ok = true;
119      long t = System.currentTimeMillis();
120      ValueSet vs = null;
121      try {
122        vs = fhirToolingClient.read(ValueSet.class, oid);
123      } catch (Exception e) {
124        if (e.getMessage().contains("timed out")) {
125          ok = false;
126        } else {
127          errs.put(oid, "Read: " +e.getMessage());
128          System.out.println("Read "+oid+" failed @ "+Utilities.describeDuration(System.currentTimeMillis()-t)+"ms: "+e.getMessage());
129          return false;
130        }
131      }
132      if (ok) {
133        t = System.currentTimeMillis();
134        try {
135          Parameters p = new Parameters();
136          p.addParameter("url", new UriType(vs.getUrl()));
137          ValueSet vse = fhirToolingClient.expandValueset(null, p);
138          vs.setExpansion(vse.getExpansion());
139        } catch (Exception e) {
140          if (e.getMessage().contains("timed out")) {
141            ok = false;
142          } else {
143            errs.put(oid, "Expansion: " +e.getMessage());
144            System.out.println("Expand "+oid+" failed @ "+Utilities.describeDuration(System.currentTimeMillis()-t)+"ms: "+e.getMessage());
145            return false;
146          }
147        }
148      }
149      if (ok) {
150        while (isIncomplete(vs.getExpansion())) {
151          Parameters p = new Parameters();
152          int offset = vs.getExpansion().getParameter("offset").getValueIntegerType().getValue() + vs.getExpansion().getParameter("count").getValueIntegerType().getValue();
153          p.addParameter("offset", offset);
154          p.addParameter("url", new UriType(vs.getUrl()));
155          t = System.currentTimeMillis();
156          try {
157            ValueSet vse = fhirToolingClient.expandValueset(null, p);    
158            vs.getExpansion().getContains().addAll(vse.getExpansion().getContains());
159            vs.getExpansion().setParameter(vse.getExpansion().getParameter());
160          } catch (Exception e2) {
161            if (e2.getMessage().contains("timed out")) {
162              ok = false;
163              break;
164            } else {
165              errs.put(oid, "Expansion: " +e2.getMessage()+" @ "+offset);
166              System.out.println("Expand "+oid+" @ "+offset+" failed @ "+Utilities.describeDuration(System.currentTimeMillis()-t)+"ms: "+e2.getMessage());
167              return false;
168            }
169          } 
170        }
171      }
172      if (ok) {
173        vs.getExpansion().setOffsetElement(null);
174        vs.getExpansion().getParameter().clear();
175
176        if (vs.hasTitle()) {
177          if (vs.getTitle().equals(vs.getDescription())) {
178            vs.setTitle(vs.getName());              
179          } else {
180            //              System.out.println(oid);
181            //              System.out.println("  name: "+vs.getName());
182            //              System.out.println("  title: "+vs.getTitle());
183            //              System.out.println("  desc: "+vs.getDescription());
184          }
185        } else {
186          vs.setTitle(vs.getName());
187        }
188        if (vs.getUrl().startsWith("https://")) {
189          System.out.println("URL is https: "+vs.getUrl());
190        }
191        vs.setName(makeValidName(vs.getName()));
192        JurisdictionUtilities.setJurisdictionCountry(vs.getJurisdiction(), "US");
193        new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path(dest, "ValueSet-" + oid + ".json")), vs);
194        return true;
195      }
196    }
197  }
198
199  private boolean isIncomplete(ValueSetExpansionComponent expansion) {
200    IntegerType c = expansion.getParameter("count") != null ? expansion.getParameter("count").getValueIntegerType() : new IntegerType(0);
201    IntegerType offset = expansion.getParameter("offset") != null ? expansion.getParameter("offset").getValueIntegerType() : new IntegerType(0);
202    return c.getValue() + offset.getValue() < expansion.getTotal();
203  }
204
205  private String makeValidName(String name) {
206    StringBuilder b = new StringBuilder();
207    boolean upper = true;
208    for (char ch : name.toCharArray()) {
209      if (ch == ' ') {
210        upper = true;
211      } else if (Character.isAlphabetic(ch)) {
212        if (upper) {
213          b.append(Character.toUpperCase(ch));
214        } else {
215          b.append(ch);
216        }
217        upper = false;
218      } else if (Character.isDigit(ch)) {
219        if (b.length() == 0) {
220          b.append('N');
221        }
222        b.append(ch);
223      } else if (ch == '_' && b.length() != 0) {
224        b.append(ch);
225      } else {
226        upper = true;
227      }
228    }
229//    System.out.println(b.toString()+" from "+name);
230    return b.toString();
231  }
232}