
org.hl7.fhir.r4.utils.sql.Runner Maven / Gradle / Ivy
The newest version!
package org.hl7.fhir.r4.utils.sql;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.fhirpath.ExpressionNode;
import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r4.fhirpath.TypeDetails;
import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r4.fhirpath.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r4.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Base64BinaryType;
import org.hl7.fhir.r4.model.BaseDateTimeType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Property;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.utils.sql.Cell;
import org.hl7.fhir.r4.utils.sql.Column;
import org.hl7.fhir.r4.utils.sql.Provider;
import org.hl7.fhir.r4.utils.sql.Storage;
import org.hl7.fhir.r4.utils.sql.Store;
import org.hl7.fhir.r4.utils.sql.Validator;
import org.hl7.fhir.r4.utils.sql.Value;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationMessage;
/**
* How to use the Runner:
*
* create a resource, and fill out:
* the context (supports the FHIRPathEngine)
* a store that handles the output
* a tracker - if you want
*
* Once it's created, you either run it as a batch, or in trickle mode
*
* (1) Batch Mode
*
* * provide a provider
* * call execute() with a ViewDefinition
* * wait... (watch with an observer if you want to track progress)
*
* (2) Trickle Mode
* * call 'prepare', and keep the WorkContext that's returned
* * each time there's a resource to process, call processResource and pass in the workContext and the resource
* * when done, call finish(WorkContext)
*/
public class Runner implements IEvaluationContext {
public interface IRunnerObserver {
public void handleRow(Base resource, int total, int cursor);
}
public class WorkContext {
private JsonObject vd;
private Store store;
protected WorkContext(JsonObject vd) {
super();
this.vd = vd;
}
}
private IWorkerContext context;
private Provider provider;
private Storage storage;
private IRunnerObserver observer;
private List prohibitedNames = new ArrayList();
private FHIRPathEngine fpe;
private String resourceName;
private List issues;
private int resCount;
public IWorkerContext getContext() {
return context;
}
public void setContext(IWorkerContext context) {
this.context = context;
}
public Provider getProvider() {
return provider;
}
public void setProvider(Provider provider) {
this.provider = provider;
}
public Storage getStorage() {
return storage;
}
public void setStorage(Storage storage) {
this.storage = storage;
}
public List getProhibitedNames() {
return prohibitedNames;
}
public void execute(JsonObject viewDefinition) {
execute("$", viewDefinition);
}
public void execute(String path, JsonObject viewDefinition) {
WorkContext wc = prepare(path, viewDefinition);
try {
evaluate(wc);
} finally {
finish(wc);
}
}
private void evaluate(WorkContext wc) {
List data = provider.fetch(resourceName);
int i = 0;
for (Base b : data) {
if (observer != null) {
observer.handleRow(b, data.size(), i);
}
processResource(wc.vd, wc.store, b);
i++;
}
}
public WorkContext prepare(String path, JsonObject viewDefinition) {
WorkContext wc = new WorkContext(viewDefinition);
if (context == null) {
throw new FHIRException("No context provided");
}
fpe = new FHIRPathEngine(context);
fpe.setHostServices(this);
fpe.setEmitSQLonFHIRWarning(true);
if (viewDefinition == null) {
throw new FHIRException("No viewDefinition provided");
}
if (provider == null) {
throw new FHIRException("No provider provided");
}
if (storage == null) {
throw new FHIRException("No storage provided");
}
Validator validator = new Validator(context, fpe, prohibitedNames, storage.supportsArrays(), storage.supportsComplexTypes(), storage.needsName());
validator.checkViewDefinition(path, viewDefinition);
issues = validator.getIssues();
validator.dump();
validator.check();
resourceName = validator.getResourceName();
wc.store = storage.createStore(wc.vd.asString("name"), (List) wc.vd.getUserData("columns"));
return wc;
}
public void processResource(WorkContext wc, Base b) {
if (observer != null) {
observer.handleRow(b, -1, resCount);
}
processResource(wc.vd, wc.store, b);
resCount++;
wc.store.flush();
}
private void processResource(JsonObject vd, Store store, Base b) {
boolean ok = true;
for (JsonObject w : vd.getJsonObjects("where")) {
String expr = w.asString("path");
ExpressionNode node = fpe.parse(expr);
boolean pass = fpe.evaluateToBoolean(vd, b, b, b, node);
if (!pass) {
ok = false;
break;
}
}
if (ok) {
List> rows = new ArrayList<>();
rows.add(new ArrayList());
for (JsonObject select : vd.getJsonObjects("select")) {
executeSelect(vd, select, b, rows);
}
for (List row : rows) {
storage.addRow(store, row);
}
}
}
public void finish(WorkContext wc) {
storage.finish(wc.store);
}
private void executeSelect(JsonObject vd, JsonObject select, Base b, List> rows) {
List focus = new ArrayList<>();
if (select.has("forEach")) {
focus.addAll(executeForEach(vd, select, b));
} else if (select.has("forEachOrNull")) {
focus.addAll(executeForEachOrNull(vd, select, b));
if (focus.isEmpty()) {
List columns = (List) select.getUserData("columns");
for (List row : rows) {
for (Column c : columns) {
Cell cell = cell(row, c.getName());
if (cell == null) {
row.add(new Cell(c, null));
}
}
}
return;
}
} else {
focus.add(b);
}
// } else if (select.has("unionAll")) {
// focus.addAll(executeUnion(select, b));
List> tempRows = new ArrayList<>();
tempRows.addAll(rows);
rows.clear();
for (Base f : focus) {
List> rowsToAdd = cloneRows(tempRows);
for (JsonObject column : select.getJsonObjects("column")) {
executeColumn(vd, column, f, rowsToAdd);
}
for (JsonObject sub : select.getJsonObjects("select")) {
executeSelect(vd, sub, f, rowsToAdd);
}
executeUnionAll(vd, select.getJsonObjects("unionAll"), f, rowsToAdd);
rows.addAll(rowsToAdd);
}
}
private void executeUnionAll(JsonObject vd, List unionList, Base b, List> rows) {
if (unionList.isEmpty()) {
return;
}
List> sourceRows = new ArrayList<>();
sourceRows.addAll(rows);
rows.clear();
for (JsonObject union : unionList) {
List> tempRows = new ArrayList<>();
tempRows.addAll(sourceRows);
executeSelect(vd, union, b, tempRows);
rows.addAll(tempRows);
}
}
private List> cloneRows(List> rows) {
List> list = new ArrayList<>();
for (List row : rows) {
list.add(cloneRow(row));
}
return list;
}
private List cloneRow(List cells) {
List list = new ArrayList<>();
for (Cell cell : cells) {
list.add(cell.copy());
}
return list;
}
private List executeForEach(JsonObject vd, JsonObject focus, Base b) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEach");
List result = new ArrayList<>();
result.addAll(fpe.evaluate(vd, b, n));
return result;
}
private List executeForEachOrNull(JsonObject vd, JsonObject focus, Base b) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEachOrNull");
List result = new ArrayList<>();
result.addAll(fpe.evaluate(vd, b, n));
return result;
}
private void executeColumn(JsonObject vd, JsonObject column, Base b, List> rows) {
ExpressionNode n = (ExpressionNode) column.getUserData("path");
List bl2 = new ArrayList<>();
if (b != null) {
bl2.addAll(fpe.evaluate(vd, b, n));
}
Column col = (Column) column.getUserData("column");
if (col == null) {
System.out.println("Error");
} else {
for (List row : rows) {
Cell c = cell(row, col.getName());
if (c == null) {
c = new Cell(col);
row.add(c);
}
if (!bl2.isEmpty()) {
if (bl2.size() + c.getValues().size() > 1) {
// this is a problem if collection != true or if the storage can't deal with it
// though this should've been picked up before now - but there are circumstances where it wouldn't be
if (!c.getColumn().isColl()) {
throw new FHIRException("The column "+c.getColumn().getName()+" is not allowed multiple values, but at least one row has multiple values");
}
}
for (Base b2 : bl2) {
c.getValues().add(genValue(c.getColumn(), b2));
}
}
}
}
}
private Value genValue(Column column, Base b) {
if (column.getKind() == null) {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an unknown column type (null) for column "+column.getName()); // can't happen
}
switch (column.getKind()) {
case Binary:
if (b instanceof Base64BinaryType) {
Base64BinaryType bb = (Base64BinaryType) b;
return Value.makeBinary(bb.primitiveValue(), bb.getValue());
} else if (b.isBooleanPrimitive()) { // ElementModel
return Value.makeBinary(b.primitiveValue(), Base64.decodeBase64(b.primitiveValue()));
} else {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a binary column for column "+column.getName());
}
case Boolean:
if (b instanceof BooleanType) {
BooleanType bb = (BooleanType) b;
return Value.makeBoolean(bb.primitiveValue(), bb.booleanValue());
} else if (b.isBooleanPrimitive()) { // ElementModel
return Value.makeBoolean(b.primitiveValue(), "true".equals(b.primitiveValue()));
} else {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a boolean column for column "+column.getName());
}
case Complex:
if (b.isPrimitive()) {
throw new FHIRException("Attempt to add a primitive type "+b.fhirType()+" to a complex column for column "+column.getName());
} else {
return Value.makeComplex(b);
}
case DateTime:
if (b instanceof BaseDateTimeType) {
BaseDateTimeType d = (BaseDateTimeType) b;
return Value.makeDate(d.primitiveValue(), d.getValue());
} else if (b.isPrimitive() && b.isDateTime()) { // ElementModel
return Value.makeDate(b.primitiveValue(), b.dateTimeValue().getValue());
} else {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName());
}
case Decimal:
if (b instanceof DecimalType) {
DecimalType d = (DecimalType) b;
return Value.makeDecimal(d.primitiveValue(), d.getValue());
} else if (b.isPrimitive()) { // ElementModel
return Value.makeDecimal(b.primitiveValue(), new BigDecimal(b.primitiveValue()));
} else {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName());
}
case Integer:
if (b instanceof IntegerType) {
IntegerType i = (IntegerType) b;
return Value.makeInteger(i.primitiveValue(), i.getValue());
} else if (b.isPrimitive()) { // ElementModel
return Value.makeInteger(b.primitiveValue(), Integer.valueOf(b.primitiveValue()));
} else {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an integer column for column "+column.getName());
}
case String:
if (b.isPrimitive()) {
return Value.makeString(b.primitiveValue());
} else {
throw new FHIRException("Attempt to add a complex type "+b.fhirType()+" to a string column for column "+column.getName());
}
case Time:
if (b.fhirType().equals("time")) {
return Value.makeString(b.primitiveValue());
} else {
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to a time column for column "+column.getName());
}
default:
throw new FHIRException("Attempt to add a type "+b.fhirType()+" to an unknown column type for column "+column.getName());
}
}
private Column column(String columnName, List columns) {
for (Column t : columns) {
if (t.getName().equalsIgnoreCase(columnName)) {
return t;
}
}
return null;
}
private Cell cell(List cells, String columnName) {
for (Cell t : cells) {
if (t.getColumn().getName().equalsIgnoreCase(columnName)) {
return t;
}
}
return null;
}
@Override
public List resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
List list = new ArrayList ();
if (explicitConstant) {
JsonObject vd = (JsonObject) appContext;
JsonObject constant = findConstant(vd, name);
if (constant != null) {
Base b = (Base) constant.getUserData("value");
if (b != null) {
list.add(b);
}
}
}
return list;
}
@Override
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
if (explicitConstant) {
JsonObject vd = (JsonObject) appContext;
JsonObject constant = findConstant(vd, name.substring(1));
if (constant != null) {
Base b = (Base) constant.getUserData("value");
if (b != null) {
return new TypeDetails(CollectionStatus.SINGLETON, b.fhirType());
}
}
}
return null;
}
private JsonObject findConstant(JsonObject vd, String name) {
for (JsonObject o : vd.getJsonObjects("constant")) {
if (name.equals(o.asString("name"))) {
return o;
}
}
return null;
}
@Override
public boolean log(String argument, List focus) {
throw new Error("Not implemented yet: log");
}
@Override
public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
switch (functionName) {
case "getResourceKey" : return new FunctionDetails("Unique Key for resource", 0, 0);
case "getReferenceKey" : return new FunctionDetails("Unique Key for resource that is the target of the reference", 0, 1);
default: return null;
}
}
@Override
public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List parameters) throws PathEngineException {
switch (functionName) {
case "getResourceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string");
case "getReferenceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string");
default: throw new Error("Not known: "+functionName);
}
}
@Override
public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters) {
switch (functionName) {
case "getResourceKey" : return executeResourceKey(focus);
case "getReferenceKey" : return executeReferenceKey(null, focus, parameters);
default: throw new Error("Not known: "+functionName);
}
}
private List executeResourceKey(List focus) {
List base = new ArrayList ();
if (focus.size() == 1) {
Base res = focus.get(0);
if (!res.hasUserData("Storage.key")) {
String key = storage.getKeyForSourceResource(res);
if (key == null) {
throw new FHIRException("Unidentified resource: "+res.fhirType()+"/"+res.getIdBase());
} else {
res.setUserData("Storage.key", key);
}
}
base.add(new StringType(res.getUserString("Storage.key")));
}
return base;
}
private List executeReferenceKey(Base rootResource, List focus, List> parameters) {
String rt = null;
if (parameters.size() > 0) {
rt = parameters.get(0).get(0).primitiveValue();
if (rt.startsWith("FHIR.")) {
rt = rt.substring(5);
}
}
List base = new ArrayList ();
if (focus.size() == 1) {
Base res = focus.get(0);
String ref = null;
if (res.fhirType().equals("Reference")) {
ref = getRef(res);
} else if (res.isPrimitive()) {
ref = res.primitiveValue();
} else {
throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType());
}
if (ref != null) {
Base target = provider.resolveReference(rootResource, ref, rt);
if (target != null) {
if (!res.hasUserData("Storage.key")) {
String key = storage.getKeyForTargetResource(target);
if (key == null) {
throw new FHIRException("Unidentified resource: "+res.fhirType()+"/"+res.getIdBase());
} else {
res.setUserData("Storage.key", key);
}
}
base.add(new StringType(res.getUserString("Storage.key")));
}
}
}
return base;
}
private String getRef(Base res) {
Property prop = res.getChildByName("reference");
if (prop != null && prop.getValues().size() == 1) {
return prop.getValues().get(0).primitiveValue();
}
return null;
}
@Override
public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException {
throw new Error("Not implemented yet: resolveReference");
}
@Override
public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException {
throw new Error("Not implemented yet: conformsToProfile");
}
@Override
public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
throw new Error("Not implemented yet: resolveValueSet");
}
@Override
public boolean paramIsType(String name, int index) {
return "getReferenceKey".equals(name);
}
public List getIssues() {
return issues;
}
}
| | | | | | | | |
© 2015 - 2025 Weber Informatics LLC | Privacy Policy