001package org.hl7.fhir.r5.utils.sql; 002 003import java.math.BigDecimal; 004import java.util.ArrayList; 005import java.util.List; 006 007import org.apache.commons.codec.binary.Base64; 008import org.hl7.fhir.exceptions.FHIRException; 009import org.hl7.fhir.exceptions.PathEngineException; 010import org.hl7.fhir.r5.context.IWorkerContext; 011import org.hl7.fhir.r5.fhirpath.ExpressionNode; 012import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 013import org.hl7.fhir.r5.fhirpath.TypeDetails; 014import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus; 015import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext; 016import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails; 017import org.hl7.fhir.r5.model.Base; 018import org.hl7.fhir.r5.model.Base64BinaryType; 019import org.hl7.fhir.r5.model.BaseDateTimeType; 020import org.hl7.fhir.r5.model.BooleanType; 021import org.hl7.fhir.r5.model.DecimalType; 022import org.hl7.fhir.r5.model.IntegerType; 023import org.hl7.fhir.r5.model.Property; 024import org.hl7.fhir.r5.model.StringType; 025import org.hl7.fhir.r5.model.ValueSet; 026import org.hl7.fhir.utilities.json.model.JsonObject; 027import org.hl7.fhir.utilities.validation.ValidationMessage; 028 029 030public class Runner implements IEvaluationContext { 031 private IWorkerContext context; 032 private Provider provider; 033 private Storage storage; 034 private List<String> prohibitedNames = new ArrayList<String>(); 035 private FHIRPathEngine fpe; 036 037 private String resourceName; 038 private List<ValidationMessage> issues; 039 040 041 public IWorkerContext getContext() { 042 return context; 043 } 044 public void setContext(IWorkerContext context) { 045 this.context = context; 046 } 047 048 049 public Provider getProvider() { 050 return provider; 051 } 052 public void setProvider(Provider provider) { 053 this.provider = provider; 054 } 055 056 public Storage getStorage() { 057 return storage; 058 } 059 public void setStorage(Storage storage) { 060 this.storage = storage; 061 } 062 063 public List<String> getProhibitedNames() { 064 return prohibitedNames; 065 } 066 067 public void execute(JsonObject viewDefinition) { 068 execute("$", viewDefinition); 069 } 070 071 public void execute(String path, JsonObject viewDefinition) { 072 if (context == null) { 073 throw new FHIRException("No context provided"); 074 } 075 fpe = new FHIRPathEngine(context); 076 fpe.setHostServices(this); 077 fpe.setEmitSQLonFHIRWarning(true); 078 if (viewDefinition == null) { 079 throw new FHIRException("No viewDefinition provided"); 080 } 081 if (provider == null) { 082 throw new FHIRException("No provider provided"); 083 } 084 if (storage == null) { 085 throw new FHIRException("No storage provided"); 086 } 087 Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName()); 088 validator.checkViewDefinition(path, viewDefinition); 089 issues = validator.getIssues(); 090 validator.dump(); 091 validator.check(); 092 resourceName = validator.getResourceName(); 093 evaluate(viewDefinition); 094 } 095 096 private void evaluate(JsonObject vd) { 097 Store store = storage.createStore(vd.asString("name"), (List<Column>) vd.getUserData("columns")); 098 099 List<Base> data = provider.fetch(resourceName); 100 101 for (Base b : data) { 102 boolean ok = true; 103 for (JsonObject w : vd.getJsonObjects("where")) { 104 String expr = w.asString("path"); 105 ExpressionNode node = fpe.parse(expr); 106 boolean pass = fpe.evaluateToBoolean(vd, b, b, b, node); 107 if (!pass) { 108 ok = false; 109 break; 110 } 111 } 112 if (ok) { 113 List<List<Cell>> rows = new ArrayList<>(); 114 rows.add(new ArrayList<Cell>()); 115 116 for (JsonObject select : vd.getJsonObjects("select")) { 117 executeSelect(vd, select, b, rows); 118 } 119 for (List<Cell> row : rows) { 120 storage.addRow(store, row); 121 } 122 } 123 } 124 storage.finish(store); 125 } 126 127 private void executeSelect(JsonObject vd, JsonObject select, Base b, List<List<Cell>> rows) { 128 List<Base> focus = new ArrayList<>(); 129 130 if (select.has("forEach")) { 131 focus.addAll(executeForEach(vd, select, b)); 132 } else if (select.has("forEachOrNull")) { 133 134 focus.addAll(executeForEachOrNull(vd, select, b)); 135 if (focus.isEmpty()) { 136 List<Column> columns = (List<Column>) select.getUserData("columns"); 137 for (List<Cell> row : rows) { 138 for (Column c : columns) { 139 Cell cell = cell(row, c.getName()); 140 if (cell == null) { 141 row.add(new Cell(c, null)); 142 } 143 } 144 } 145 return; 146 } 147 } else { 148 focus.add(b); 149 } 150 151// } else if (select.has("unionAll")) { 152// focus.addAll(executeUnion(select, b)); 153 154 List<List<Cell>> tempRows = new ArrayList<>(); 155 tempRows.addAll(rows); 156 rows.clear(); 157 158 for (Base f : focus) { 159 List<List<Cell>> rowsToAdd = cloneRows(tempRows); 160 161 for (JsonObject column : select.getJsonObjects("column")) { 162 executeColumn(vd, column, f, rowsToAdd); 163 } 164 165 for (JsonObject sub : select.getJsonObjects("select")) { 166 executeSelect(vd, sub, f, rowsToAdd); 167 } 168 169 executeUnionAll(vd, select.getJsonObjects("unionAll"), f, rowsToAdd); 170 171 rows.addAll(rowsToAdd); 172 } 173 } 174 175 private void executeUnionAll(JsonObject vd, List<JsonObject> unionList, Base b, List<List<Cell>> rows) { 176 if (unionList.isEmpty()) { 177 return; 178 } 179 List<List<Cell>> sourceRows = new ArrayList<>(); 180 sourceRows.addAll(rows); 181 rows.clear(); 182 183 for (JsonObject union : unionList) { 184 List<List<Cell>> tempRows = new ArrayList<>(); 185 tempRows.addAll(sourceRows); 186 executeSelect(vd, union, b, tempRows); 187 rows.addAll(tempRows); 188 } 189 } 190 191 private List<List<Cell>> cloneRows(List<List<Cell>> rows) { 192 List<List<Cell>> list = new ArrayList<>(); 193 for (List<Cell> row : rows) { 194 list.add(cloneRow(row)); 195 } 196 return list; 197 } 198 199 private List<Cell> cloneRow(List<Cell> cells) { 200 List<Cell> list = new ArrayList<>(); 201 for (Cell cell : cells) { 202 list.add(cell.copy()); 203 } 204 return list; 205 } 206 207 private List<Base> executeForEach(JsonObject vd, JsonObject focus, Base b) { 208 ExpressionNode n = (ExpressionNode) focus.getUserData("forEach"); 209 List<Base> result = new ArrayList<>(); 210 result.addAll(fpe.evaluate(vd, b, n)); 211 return result; 212 } 213 214 private List<Base> executeForEachOrNull(JsonObject vd, JsonObject focus, Base b) { 215 ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull"); 216 List<Base> result = new ArrayList<>(); 217 result.addAll(fpe.evaluate(vd, b, n)); 218 return result; 219 } 220 221 private void executeColumn(JsonObject vd, JsonObject column, Base b, List<List<Cell>> rows) { 222 ExpressionNode n = (ExpressionNode) column.getUserData("path"); 223 List<Base> bl2 = new ArrayList<>(); 224 if (b != null) { 225 bl2.addAll(fpe.evaluate(vd, b, n)); 226 } 227 Column col = (Column) column.getUserData("column"); 228 if (col == null) { 229 System.out.println("Error"); 230 } else { 231 for (List<Cell> row : rows) { 232 Cell c = cell(row, col.getName()); 233 if (c == null) { 234 c = new Cell(col); 235 row.add(c); 236 } 237 if (!bl2.isEmpty()) { 238 if (bl2.size() + c.getValues().size() > 1) { 239 // this is a problem if collection != true or if the storage can't deal with it 240 // though this should've been picked up before now - but there are circumstances where it wouldn't be 241 if (!c.getColumn().isColl()) { 242 throw new FHIRException("The column "+c.getColumn().getName()+" is not allowed multiple values, but at least one row has multiple values"); 243 } 244 } 245 for (Base b2 : bl2) { 246 c.getValues().add(genValue(c.getColumn(), b2)); 247 } 248 } 249 } 250 } 251 } 252 253 254 private Value genValue(Column column, Base b) { 255 if (column.getKind() == null) { 256 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an unknown column type (null) for column "+column.getName()); // can't happen 257 } 258 switch (column.getKind()) { 259 case Binary: 260 if (b instanceof Base64BinaryType) { 261 Base64BinaryType bb = (Base64BinaryType) b; 262 return Value.makeBinary(bb.primitiveValue(), bb.getValue()); 263 } else if (b.isBooleanPrimitive()) { // ElementModel 264 return Value.makeBinary(b.primitiveValue(), Base64.decodeBase64(b.primitiveValue())); 265 } else { 266 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a binary column for column "+column.getName()); 267 } 268 case Boolean: 269 if (b instanceof BooleanType) { 270 BooleanType bb = (BooleanType) b; 271 return Value.makeBoolean(bb.primitiveValue(), bb.booleanValue()); 272 } else if (b.isBooleanPrimitive()) { // ElementModel 273 return Value.makeBoolean(b.primitiveValue(), "true".equals(b.primitiveValue())); 274 } else { 275 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a boolean column for column "+column.getName()); 276 } 277 case Complex: 278 if (b.isPrimitive()) { 279 throw new FHIRException("Attempt to add a primitive type "+b.fhirType()+" to a complex column for column "+column.getName()); 280 } else { 281 return Value.makeComplex(b); 282 } 283 case DateTime: 284 if (b instanceof BaseDateTimeType) { 285 BaseDateTimeType d = (BaseDateTimeType) b; 286 return Value.makeDate(d.primitiveValue(), d.getValue()); 287 } else if (b.isPrimitive() && b.isDateTime()) { // ElementModel 288 return Value.makeDate(b.primitiveValue(), b.dateTimeValue().getValue()); 289 } else { 290 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName()); 291 } 292 case Decimal: 293 if (b instanceof DecimalType) { 294 DecimalType d = (DecimalType) b; 295 return Value.makeDecimal(d.primitiveValue(), d.getValue()); 296 } else if (b.isPrimitive()) { // ElementModel 297 return Value.makeDecimal(b.primitiveValue(), new BigDecimal(b.primitiveValue())); 298 } else { 299 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName()); 300 } 301 case Integer: 302 if (b instanceof IntegerType) { 303 IntegerType i = (IntegerType) b; 304 return Value.makeInteger(i.primitiveValue(), i.getValue()); 305 } else if (b.isPrimitive()) { // ElementModel 306 return Value.makeInteger(b.primitiveValue(), Integer.valueOf(b.primitiveValue())); 307 } else { 308 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName()); 309 } 310 case String: 311 if (b.isPrimitive()) { 312 return Value.makeString(b.primitiveValue()); 313 } else { 314 throw new FHIRException("Attempt to add a complex type "+b.fhirType()+" to a string column for column "+column.getName()); 315 } 316 case Time: 317 if (b.fhirType().equals("time")) { 318 return Value.makeString(b.primitiveValue()); 319 } else { 320 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a time column for column "+column.getName()); 321 } 322 default: 323 throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an unknown column type for column "+column.getName()); 324 } 325 } 326 327 private Column column(String columnName, List<Column> columns) { 328 for (Column t : columns) { 329 if (t.getName().equalsIgnoreCase(columnName)) { 330 return t; 331 } 332 } 333 return null; 334 } 335 336 private Cell cell(List<Cell> cells, String columnName) { 337 for (Cell t : cells) { 338 if (t.getColumn().getName().equalsIgnoreCase(columnName)) { 339 return t; 340 } 341 } 342 return null; 343 } 344 345 @Override 346 public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException { 347 List<Base> list = new ArrayList<Base>(); 348 if (explicitConstant) { 349 JsonObject vd = (JsonObject) appContext; 350 JsonObject constant = findConstant(vd, name); 351 if (constant != null) { 352 Base b = (Base) constant.getUserData("value"); 353 if (b != null) { 354 list.add(b); 355 } 356 } 357 } 358 return list; 359 } 360 361 @Override 362 public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException { 363 if (explicitConstant) { 364 JsonObject vd = (JsonObject) appContext; 365 JsonObject constant = findConstant(vd, name.substring(1)); 366 if (constant != null) { 367 Base b = (Base) constant.getUserData("value"); 368 if (b != null) { 369 return new TypeDetails(CollectionStatus.SINGLETON, b.fhirType()); 370 } 371 } 372 } 373 return null; 374 } 375 376 private JsonObject findConstant(JsonObject vd, String name) { 377 for (JsonObject o : vd.getJsonObjects("constant")) { 378 if (name.equals(o.asString("name"))) { 379 return o; 380 } 381 } 382 return null; 383 } 384 @Override 385 public boolean log(String argument, List<Base> focus) { 386 throw new Error("Not implemented yet: log"); 387 } 388 389 @Override 390 public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) { 391 switch (functionName) { 392 case "getResourceKey" : return new FunctionDetails("Unique Key for resource", 0, 0); 393 case "getReferenceKey" : return new FunctionDetails("Unique Key for resource that is the target of the reference", 0, 1); 394 default: return null; 395 } 396 } 397 @Override 398 public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List<TypeDetails> parameters) throws PathEngineException { 399 switch (functionName) { 400 case "getResourceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string"); 401 case "getReferenceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string"); 402 default: throw new Error("Not known: "+functionName); 403 } 404 } 405 406 @Override 407 public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) { 408 switch (functionName) { 409 case "getResourceKey" : return executeResourceKey(focus); 410 case "getReferenceKey" : return executeReferenceKey(null, focus, parameters); 411 default: throw new Error("Not known: "+functionName); 412 } 413 } 414 415 private List<Base> executeResourceKey(List<Base> focus) { 416 List<Base> base = new ArrayList<Base>(); 417 if (focus.size() == 1) { 418 Base res = focus.get(0); 419 if (!res.hasUserData("Storage.key")) { 420 String key = storage.getKeyForSourceResource(res); 421 if (key == null) { 422 throw new FHIRException("Unidentified resource: "+res.fhirType()+"/"+res.getIdBase()); 423 } else { 424 res.setUserData("Storage.key", key); 425 } 426 } 427 base.add(new StringType(res.getUserString("Storage.key"))); 428 } 429 return base; 430 } 431 432 private List<Base> executeReferenceKey(Base rootResource, List<Base> focus, List<List<Base>> parameters) { 433 String rt = null; 434 if (parameters.size() > 0) { 435 rt = parameters.get(0).get(0).primitiveValue(); 436 if (rt.startsWith("FHIR.")) { 437 rt = rt.substring(5); 438 } 439 } 440 List<Base> base = new ArrayList<Base>(); 441 if (focus.size() == 1) { 442 Base res = focus.get(0); 443 String ref = null; 444 if (res.fhirType().equals("Reference")) { 445 ref = getRef(res); 446 } else if (res.isPrimitive()) { 447 ref = res.primitiveValue(); 448 } else { 449 throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType()); 450 } 451 if (ref != null) { 452 Base target = provider.resolveReference(rootResource, ref, rt); 453 if (target != null) { 454 if (!res.hasUserData("Storage.key")) { 455 String key = storage.getKeyForTargetResource(target); 456 if (key == null) { 457 throw new FHIRException("Unidentified resource: "+res.fhirType()+"/"+res.getIdBase()); 458 } else { 459 res.setUserData("Storage.key", key); 460 } 461 } 462 base.add(new StringType(res.getUserString("Storage.key"))); 463 } 464 } 465 } 466 return base; 467 } 468 469 private String getRef(Base res) { 470 Property prop = res.getChildByName("reference"); 471 if (prop != null && prop.getValues().size() == 1) { 472 return prop.getValues().get(0).primitiveValue(); 473 } 474 return null; 475 } 476 477 @Override 478 public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException { 479 throw new Error("Not implemented yet: resolveReference"); 480 } 481 482 @Override 483 public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException { 484 throw new Error("Not implemented yet: conformsToProfile"); 485 } 486 487 @Override 488 public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { 489 throw new Error("Not implemented yet: resolveValueSet"); 490 } 491 @Override 492 public boolean paramIsType(String name, int index) { 493 return "getReferenceKey".equals(name); 494 } 495 public List<ValidationMessage> getIssues() { 496 return issues; 497 } 498 499 500}