001package org.hl7.fhir.r4.utils;
002
003import java.io.BufferedReader;
004import java.io.IOException;
005import java.io.InputStreamReader;
006import java.net.URISyntaxException;
007import java.util.List;
008
009import org.hl7.fhir.exceptions.FHIRException;
010import org.hl7.fhir.r4.context.SimpleWorkerContext;
011import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
012import org.hl7.fhir.r4.formats.IParser.OutputStyle;
013import org.hl7.fhir.r4.formats.JsonParser;
014import org.hl7.fhir.r4.model.Base;
015import org.hl7.fhir.r4.model.BooleanType;
016import org.hl7.fhir.r4.model.Bundle;
017import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
018import org.hl7.fhir.r4.model.CapabilityStatement;
019import org.hl7.fhir.r4.model.CodeSystem;
020import org.hl7.fhir.r4.model.CodeType;
021import org.hl7.fhir.r4.model.CodeableConcept;
022import org.hl7.fhir.r4.model.Coding;
023import org.hl7.fhir.r4.model.Condition;
024import org.hl7.fhir.r4.model.DateType;
025import org.hl7.fhir.r4.model.HumanName;
026import org.hl7.fhir.r4.model.Immunization;
027import org.hl7.fhir.r4.model.Parameters;
028import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
029import org.hl7.fhir.r4.model.Patient;
030import org.hl7.fhir.r4.model.Period;
031import org.hl7.fhir.r4.model.Procedure;
032import org.hl7.fhir.r4.model.Property;
033import org.hl7.fhir.r4.model.Resource;
034import org.hl7.fhir.r4.model.StringType;
035import org.hl7.fhir.r4.model.Type;
036import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
037import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
038import org.hl7.fhir.utilities.Utilities;
039import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
040import org.hl7.fhir.utilities.npm.NpmPackage;
041
042@MarkedToMoveToAdjunctPackage
043@SuppressWarnings("checkstyle:systemout")
044public class CmdLineApp {
045
046  private FHIRToolingClient client;
047  private String currentId;
048  private Resource currentResource;
049  private SimpleWorkerContext context;
050  private FHIRPathEngine fpe;
051
052  public static void main(String[] args) throws IOException, Exception {
053    new CmdLineApp().execute();
054  }
055
056  private void execute() throws IOException {
057    System.out.print("Loading...");
058    NpmPackage npm = new FilesystemPackageCacheManager.Builder().build().loadPackage("hl7.fhir.r4.core");
059    context = SimpleWorkerContext.fromPackage(npm);
060    fpe = new FHIRPathEngine(context);
061    System.out.println(" Done");
062    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
063    genMenu();
064    boolean finished = false;
065    do {
066      System.out.print("> ");
067      String cmd = reader.readLine();
068      String[] p = cmd.split("\\s+");
069      try {
070        if (p.length == 1 && p[0].equals("x")) {
071          finished = true;
072        } else if (p.length == 1 && p[0].equals("?")) {
073          genMenu();
074        } else if (p.length == 1 && p[0].equals("?tx")) {
075          genMenuTx();
076        } else if (p.length >= 1 && p[0].equals("c")) {
077          if (p.length == 1) {
078            connectToServer("http://hapi.fhir.org/baseR4");
079          } else {
080            connectToServer(p[1]);
081          }
082        } else if (p.length >= 1 && p[0].equals("imm")) {
083          if (p.length == 1) {
084            if (currentResource == null || !(currentResource instanceof Patient)) {
085              throw new FHIRException("Current resource must be a patient for this command");
086            }
087            getImmunizations();
088          } else {
089            select("Immunization", p[1]);
090          }
091        } else if (p.length >= 1 && p[0].equals("cnd")) {
092          if (p.length == 1) {
093            if (currentResource == null || !(currentResource instanceof Patient)) {
094              throw new FHIRException("Current resource must be a patient for this command");
095            }
096            getConditions();
097          } else {
098            select("Condition", p[1]);
099          }
100        } else if (p.length >= 1 && p[0].equals("prc")) {
101          if (p.length == 1) {
102            if (currentResource == null || !(currentResource instanceof Patient)) {
103              throw new FHIRException("Current resource must be a patient for this command");
104            }
105            getProcedures();
106          } else {
107            select("Procedure", p[1]);
108          }
109        } else if (p.length >= 1 && p[0].equals("v")) {
110          if (p.length == 1) {
111            viewResource();
112          } else {
113            viewResource(p[1]);
114          }
115        } else if (p.length >= 2 && p[0].equals("s")) {
116          search(p);
117        } else if (p.length >= 2 && p[0].equals("p")) {
118          select("Patient", p[1]);
119        } else if (p.length == 3 && p[0].equals("e")) {
120          edit(p[1], p[2]);
121        } else if (p.length > 3 && p[0].equals("tx")) {
122          tx(p);
123        } else {
124          System.out.println("Command unknown or not understood: "+cmd);
125        }
126      } catch (Exception e) {
127        System.out.println("Error executing command "+p[0]+": "+e.getMessage());
128      }
129    } while (!finished);
130    
131    System.out.println("Finished!"); 
132  }
133
134  private boolean tx(String[] p) throws IOException {
135    if (p[1].equals("l")) {
136      if (p.length == 4) {
137        return lookup(p[2], p[3]);
138      }
139    } else if (p[1].equals("v")) {
140      if (p.length == 4) {
141        return validate(p[2], p[3], null);
142      } else if (p.length == 5) {
143        return validate(p[2], p[3], p[4]);
144      }
145    }
146    throw new FHIRException("Not understood");
147  }
148
149  private boolean validate(String system, String code, String url) throws IOException {
150    Parameters pin = new Parameters();
151    if (url != null) {
152      pin.addParameter("url", url);
153    }
154    pin.addParameter("system", system);
155    pin.addParameter("code", code);
156    Parameters pout = client.operateType(CodeSystem.class, "validate-code", pin);
157    showParameters("  ", pout.getParameter());
158    return true;
159  }
160
161  private void showParameters(String prefix, List<ParametersParameterComponent> list) {
162    for (ParametersParameterComponent pp : list) {
163      if (pp.hasValue()) {        
164        System.out.println("  "+pp.getName()+": "+pp.getValue().toString());
165      } else if (pp.hasPart()) {
166        showParameters("   ", pp.getPart());
167      }
168    }
169  }
170
171  private boolean lookup(String string, String string2) {
172    throw new FHIRException("Not implemented");
173  }
174
175  private void getImmunizations() throws IOException {
176    Bundle bnd = client.search("Immunization", "?patient="+currentId);
177    System.out.println(""+bnd.getTotal()+" Immunizations found. Printing "+bnd.getEntry().size());
178    
179    for (BundleEntryComponent be : bnd.getEntry()) {
180      Resource imm = be.getResource();
181      System.out.println(summary(imm));
182    } 
183  }
184
185  private void getProcedures() throws IOException {
186
187    Bundle bnd = client.search("Procedure", "?patient="+currentId);
188    System.out.println(""+bnd.getTotal()+" Procedures found. Printing "+bnd.getEntry().size());
189    
190    for (BundleEntryComponent be : bnd.getEntry()) {
191      Resource imm = be.getResource();
192      System.out.println(summary(imm));
193    } 
194  }
195  
196  private void getConditions() throws IOException {
197
198    Bundle bnd = client.search("Condition", "?patient="+currentId);
199    System.out.println(""+bnd.getTotal()+" Conditions found. Printing "+bnd.getEntry().size());
200    
201    for (BundleEntryComponent be : bnd.getEntry()) {
202      Resource imm = be.getResource();
203      System.out.println(summary(imm));
204    } 
205  }
206
207  private void edit(String path, String value) {
208    if (path.contains(".")) {
209      List<Base> list = fpe.evaluate(currentResource, fpe.parse(path.substring(0, path.lastIndexOf("."))));
210      if (list.size() == 1) {
211        path = path.substring(path.lastIndexOf(".")+1);
212        Property p = list.get(0).getNamedProperty(path);
213        Base b = makeValue(p, value);
214        list.get(0).setProperty(path, b);
215      } else {
216        throw new FHIRException("Unable to set value at "+path+": "+list.size()+" matches");
217      }
218    } else {
219      Property p = currentResource.getNamedProperty(path);
220      Base b = makeValue(p, value);
221      currentResource.setProperty(path, b);
222    }
223    currentResource = client.update(currentResource);
224  }
225
226  private Base makeValue(Property p, String value) {
227    switch (p.getTypeCode()) {
228    case "boolean" : return new BooleanType(value);
229    case "code" :  return new CodeType(value);
230    case "string" : return new StringType(value);
231    case "date" : return new DateType(value);
232    }
233    throw new FHIRException("Unhandled type "+p.getTypeCode());
234  }
235
236  private void viewResource(String path) {
237    System.out.println("Current Resource, query = "+path);
238    List<Base> list = fpe.evaluate(currentResource, fpe.parse(path));
239    for (Base b : list) {
240      System.out.println(b.toString());
241    }
242  }
243
244  private void viewResource() throws IOException {
245    System.out.println("Current Resource:");
246    System.out.println(new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(currentResource));
247  }
248
249  private void select(String type, String id) throws IOException {
250    if (type.equals("Patient")) {
251      currentResource = client.fetchResource(Patient.class, id);
252    } else if (type.equals("Immunization")) {
253      currentResource = client.fetchResource(Immunization.class, id);
254    } else if (type.equals("Condition")) {
255      currentResource = client.fetchResource(Condition.class, id);
256    } else if (type.equals("Procedure")) {
257      currentResource = client.fetchResource(Procedure.class, id);
258    } else {
259      throw new FHIRException("Unhandled type "+type);
260    }
261    currentId = type+"/"+id;
262    System.out.println("Resource = "+currentId+" "+summary(currentResource));
263  }
264
265  private String summary(Resource r) throws IOException {
266//    return new XmlParser().composeString(r);
267    if (r instanceof Patient) {
268      Patient pat = (Patient) r;
269      return pat.getIdBase()+" "+pat.getGender()+" "+pat.getBirthDateElement().asStringValue()+" "+name(pat);
270    }
271    if (r instanceof Immunization) {
272      Immunization imm = (Immunization) r;
273      return imm.getIdBase()+" "+imm.getOccurrenceDateTimeType().asStringValue()+" "+code(imm.getVaccineCode())+" "+imm.getLotNumber()+" ("+imm.getStatus()+")";
274    }
275    if (r instanceof Condition) {
276      Condition cnd = (Condition) r;
277      return cnd.getIdBase()+" "+code(cnd.getClinicalStatus())+" "+code(cnd.getVerificationStatus())+" "+code(cnd.getCode())+" "+cnd.getRecordedDateElement().asStringValue();
278    }
279    if (r instanceof Procedure) {
280      Procedure prc = (Procedure) r;
281      return prc.getIdBase()+" "+prc.getStatusElement().asStringValue()+" "+code(prc.getCode())+" "+code(prc.getCode())+" "+dt(prc.getPerformed());
282    }
283    return "??";
284  }
285
286  private String dt(Type type) {
287    if (type == null) {
288      return "";
289    }
290    if (type.isPrimitive()) {
291      return type.primitiveValue();
292    }
293    if (type instanceof Period) {
294      Period pd = (Period) type;
295      return pd.getStartElement().asStringValue()+" -> "+pd.getEndElement().asStringValue();
296    }
297    return "??";
298  }
299
300  private String code(CodeableConcept cc) {
301    for (Coding c : cc.getCoding()) {
302      if (c.hasSystem()) {
303        String d = c.hasDisplay() ? " ("+c.getDisplay()+")" : "";
304        if (c.hasCode()) {
305          switch (c.getSystem()) {
306          case "http://hl7.org/fhir/sid/cvx": return "CVX "+c.getCode()+d;
307          case "http://snomed.info/sct": return "SCT "+c.getCode()+d;
308          default:
309            if (Utilities.startsWithInList(c.getSystem(), "http://terminology.hl7.org")) {
310              return c.getCode();
311            } else {
312              throw new FHIRException("Unknown system "+c.getSystem());
313            }
314          }
315        }
316      }
317    }
318    for (Coding c : cc.getCoding()) {
319      if (c.hasCode()) {
320        return c.getCode();
321      }
322    }
323    if (cc.hasText()) {
324      return cc.getText();
325    }
326    return "";
327  }
328
329  private void search(String[] p) throws IOException {
330    if (client == null) {
331      throw new FHIRException("Not connected to to a server");
332    }
333    String search = "?name="+p[1];
334    if (p.length > 2) {
335      search = search +"&gender="+p[2];
336      if (p.length > 3) {
337        search = search +"&birthdate="+p[3];
338      }
339    }
340    Bundle bnd = client.search("Patient", search);
341    System.out.println(""+bnd.getTotal()+" Patients found. Printing "+bnd.getEntry().size());
342    
343    for (BundleEntryComponent be : bnd.getEntry()) {
344      Patient pat = (Patient) be.getResource();
345      System.out.println(summary(pat));
346    } 
347  }
348
349  private String name(Patient pat) {
350    if (pat.getName().size() > 0) {
351      return name(pat.getName().get(0));
352    }
353    return null;
354  }
355
356  private String name(HumanName n) {
357    if (n.hasText()) {
358      return n.getText();
359    }
360    if (n.hasFamily()) {
361      if (n.hasGiven()) {
362        return n.getGiven().get(0)+" "+n.getFamily().toUpperCase();
363      } else {
364        return n.getFamily().toUpperCase();
365      }
366    }
367    return "??";
368  }
369
370  private void connectToServer(String url) throws URISyntaxException {
371    client = new FHIRToolingClient(url, "FHIR-Command-Line-App");
372    CapabilityStatement cs = client.getCapabilitiesStatementQuick();
373    System.out.println("Connected to "+url+": "+cs.getSoftware().getName()+", version "+cs.getFhirVersion().toCode());
374  }
375
376  private void genMenuTx() {
377
378    System.out.println("Simple Client. Commands you can run:");
379    System.out.println(" tx l {system} {code}");
380    System.out.println(" tx v {system} {code} {vs-url}");
381    System.out.println(" ?tx - print this again");    
382    System.out.println(" x - exit");    
383  }
384  
385  private void genMenu() {
386    System.out.println("Simple Client. Commands you can run:");
387    System.out.println(" c {url} - connect to a server");
388    System.out.println(" s {name} [{gender}] {{dob]} - find a patient record");
389    System.out.println(" p {id} - choose a patient record");    
390    System.out.println(" v [{field}] - see a set of field(s) in the current resource, or the whole resource");
391    System.out.println(" e {field} {value} - edit a field in the current resource");
392    System.out.println(" imm [{id}] - get a list of the patient's immunizations, or use the resource for the id (then use e to edit it)");
393    System.out.println(" cnd [{id}] - get a list of the patient's conditions, or use the resource for the id (then use e to edit it)");
394    System.out.println(" prc [{id}] - get a list of the patient's procedures, or use the resource for the id (then use e to edit it)");
395    System.out.println(" ? - print this again");    
396    System.out.println(" x - exit");    
397  }
398}