
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}