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