Merge pull request #1449 from hapifhir/2023-10-gg-xig_steps

2023 10 gg xig steps
This commit is contained in:
Grahame Grieve 2023-10-04 03:57:22 +03:00 committed by GitHub
commit d6eb345692
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1803 additions and 73 deletions

View File

@ -72,6 +72,7 @@ public class PackageVisitor {
private FilesystemPackageCacheManager pcm;
private PackageClient pc;
private String cache;
private int step;
public List<String> getResourceTypes() {
return resourceTypes;
@ -164,15 +165,17 @@ public class PackageVisitor {
if (pid != null) {
if (!cpidSet.contains(pid)) {
cpidSet.add(pid);
List<String> vList = listVersions(pid);
if (oldVersions) {
for (String v : vList) {
processPackage(pid, v, i, pidList.size());
if (step == 0 || step == 3) {
List<String> vList = listVersions(pid);
if (oldVersions) {
for (String v : vList) {
processPackage(pid, v, i, pidList.size());
}
} else if (vList.isEmpty()) {
System.out.println("No Packages for "+pid);
} else {
processPackage(pid, vList.get(vList.size() - 1), i, pidList.size());
}
} else if (vList.isEmpty()) {
System.out.println("No Packages for "+pid);
} else {
processPackage(pid, vList.get(vList.size() - 1), i, pidList.size());
}
} else {
processor.alreadyVisited(pid);
@ -180,76 +183,81 @@ public class PackageVisitor {
i++;
}
}
JsonObject json = JsonParser.parseObjectFromUrl("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json");
i = 0;
List<JsonObject> objects = json.getJsonObjects("guides");
for (JsonObject o : objects) {
String pid = o.asString("npm-name");
if (pid != null && !cpidSet.contains(pid)) {
cpidSet.add(pid);
List<String> vList = listVersions(pid);
if (oldVersions) {
for (String v : vList) {
processPackage(pid, v, i, objects.size());
if (step == 0 || step == 3) {
JsonObject json = JsonParser.parseObjectFromUrl("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json");
i = 0;
List<JsonObject> objects = json.getJsonObjects("guides");
for (JsonObject o : objects) {
String pid = o.asString("npm-name");
if (pid != null && !cpidSet.contains(pid)) {
cpidSet.add(pid);
List<String> vList = listVersions(pid);
if (oldVersions) {
for (String v : vList) {
processPackage(pid, v, i, objects.size());
}
} else if (vList.isEmpty()) {
System.out.println("No Packages for "+pid);
} else {
processPackage(pid, vList.get(vList.size() - 1), i, objects.size());
}
} else if (vList.isEmpty()) {
System.out.println("No Packages for "+pid);
} else {
processPackage(pid, vList.get(vList.size() - 1), i, objects.size());
}
i++;
}
i++;
}
}
private void processCurrentPackage(String url, String pid, Set<String> cpidSet, int i, int t) {
try {
long ms1 = System.currentTimeMillis();
String[] p = url.split("\\/");
String repo = "https://build.fhir.org/ig/"+p[0]+"/"+p[1];
JsonObject manifest = JsonParser.parseObjectFromUrl(repo+"/package.manifest.json");
File co = new File(Utilities.path(cache, pid+"."+manifest.asString("date")+".tgz"));
if (!co.exists()) {
SimpleHTTPClient fetcher = new SimpleHTTPClient();
HTTPResult res = fetcher.get(repo+"/package.tgz?nocache=" + System.currentTimeMillis());
res.checkThrowException();
TextFile.bytesToFile(res.getContent(), co);
}
NpmPackage npm = NpmPackage.fromPackage(new FileInputStream(co));
String fv = npm.fhirVersion();
cpidSet.add(pid);
long ms2 = System.currentTimeMillis();
if (step == 0 || (step == 1 && i < t/2) || (step == 2 && i >= t/2)) {
long ms1 = System.currentTimeMillis();
String[] p = url.split("\\/");
String repo = "https://build.fhir.org/ig/"+p[0]+"/"+p[1];
JsonObject manifest = JsonParser.parseObjectFromUrl(repo+"/package.manifest.json");
File co = new File(Utilities.path(cache, pid+"."+manifest.asString("date")+".tgz"));
if (!co.exists()) {
SimpleHTTPClient fetcher = new SimpleHTTPClient();
HTTPResult res = fetcher.get(repo+"/package.tgz?nocache=" + System.currentTimeMillis());
res.checkThrowException();
TextFile.bytesToFile(res.getContent(), co);
}
NpmPackage npm = NpmPackage.fromPackage(new FileInputStream(co));
String fv = npm.fhirVersion();
long ms2 = System.currentTimeMillis();
if (corePackages || !corePackage(npm)) {
if (fv != null && (versions.isEmpty() || versions.contains(fv))) {
PackageContext ctxt = new PackageContext(pid+"#current", npm, fv);
boolean ok = false;
Object context = null;
try {
context = processor.startPackage(ctxt);
ok = true;
} catch (Exception e) {
System.out.println("####### Error loading "+pid+"#current["+fv+"]: ####### "+e.getMessage());
// e.printStackTrace();
}
if (ok) {
int c = 0;
for (String type : resourceTypes) {
for (String s : npm.listResources(type)) {
c++;
try {
processor.processResource(ctxt, context, type, s, TextFile.streamToBytes(npm.load("package", s)));
} catch (Exception e) {
System.out.println("####### Error loading "+pid+"#current["+fv+"]/"+type+" ####### "+e.getMessage());
// e.printStackTrace();
if (corePackages || !corePackage(npm)) {
if (fv != null && (versions.isEmpty() || versions.contains(fv))) {
PackageContext ctxt = new PackageContext(pid+"#current", npm, fv);
boolean ok = false;
Object context = null;
try {
context = processor.startPackage(ctxt);
ok = true;
} catch (Exception e) {
System.out.println("####### Error loading "+pid+"#current["+fv+"]: ####### "+e.getMessage());
// e.printStackTrace();
}
if (ok) {
int c = 0;
for (String type : resourceTypes) {
for (String s : npm.listResources(type)) {
c++;
try {
processor.processResource(ctxt, context, type, s, TextFile.streamToBytes(npm.load("package", s)));
} catch (Exception e) {
System.out.println("####### Error loading "+pid+"#current["+fv+"]/"+type+" ####### "+e.getMessage());
// e.printStackTrace();
}
}
}
processor.finishPackage(ctxt);
System.out.println("Processed: "+pid+"#current: "+c+" resources ("+i+" of "+t+", "+(ms2-ms1)+"/"+(System.currentTimeMillis()-ms2)+"ms)");
}
processor.finishPackage(ctxt);
System.out.println("Processed: "+pid+"#current: "+c+" resources ("+i+" of "+t+", "+(ms2-ms1)+"/"+(System.currentTimeMillis()-ms2)+"ms)");
} else {
System.out.println("Ignored: "+pid+"#current: no version");
}
} else {
System.out.println("Ignored: "+pid+"#current: no version");
}
}
} catch (Exception e) {
@ -370,4 +378,12 @@ public class PackageVisitor {
npm.name().startsWith("hl7.fhir.r5."));
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
}

View File

@ -4507,7 +4507,7 @@ public class ProfileUtilities extends TranslatingUtilities {
public static boolean isModifierExtension(StructureDefinition sd) {
ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension");
return defn.getIsModifier();
return defn != null && defn.getIsModifier();
}
public boolean isForPublication() {

View File

@ -711,5 +711,23 @@ public class ExpressionNode {
public void setOpTypes(TypeDetails opTypes) {
this.opTypes = opTypes;
}
public List<String> getDistalNames() {
List<String> names = new ArrayList<String>();
if (operation != null) {
names.add(null);
} else if (inner != null) {
names.addAll(inner.getDistalNames());
} else if (group != null) {
names.addAll(group.getDistalNames());
} else if (function != null) {
names.addAll(null);
} else if (constant != null) {
names.addAll(null);
} else {
names.add(name);
}
return names;
}
}

View File

@ -536,6 +536,13 @@ public class FHIRPathEngine {
return res;
}
public TypeDetails checkOnTypes(Object appContext, String resourceType, TypeDetails types, ExpressionNode expr, List<String> warnings) throws FHIRLexerException, PathEngineException, DefinitionException {
typeWarnings.clear();
TypeDetails res = executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, null, true, false);
warnings.addAll(typeWarnings);
return res;
}
/**
* check that paths referred to in the ExpressionNode are valid
*
@ -1361,7 +1368,7 @@ public class FHIRPathEngine {
case Unescape: return checkParamCount(lexer, location, exp, 1);
case Trim: return checkParamCount(lexer, location, exp, 0);
case Split: return checkParamCount(lexer, location, exp, 1);
case Join: return checkParamCount(lexer, location, exp, 1);
case Join: return checkParamCount(lexer, location, exp, 0, 1);
case HtmlChecks1: return checkParamCount(lexer, location, exp, 0);
case HtmlChecks2: return checkParamCount(lexer, location, exp, 0);
case Comparable: return checkParamCount(lexer, location, exp, 1);
@ -4209,12 +4216,16 @@ public class FHIRPathEngine {
}
private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String param = nl.get(0).primitiveValue();
String param2 = param;
if (exp.getParameters().size() == 2) {
nl = execute(context, focus, exp.getParameters().get(1), true);
param2 = nl.get(0).primitiveValue();
List<Base> nl = exp.getParameters().size() > 0 ? execute(context, focus, exp.getParameters().get(0), true) : new ArrayList<Base>();
String param = "";
String param2 = "";
if (exp.getParameters().size() > 0) {
param = nl.get(0).primitiveValue();
param2 = param;
if (exp.getParameters().size() == 2) {
nl = execute(context, focus, exp.getParameters().get(1), true);
param2 = nl.get(0).primitiveValue();
}
}
List<Base> result = new ArrayList<Base>();

View File

@ -0,0 +1,33 @@
package org.hl7.fhir.r5.utils.sql;
import java.util.ArrayList;
import java.util.List;
public class Cell {
private Column column;
private List<Value> values = new ArrayList<>();
public Cell(Column column) {
super();
this.column = column;
}
public Column getColumn() {
return column;
}
public List<Value> getValues() {
return values;
}
public Cell copy() {
Cell cell = new Cell(column);
for (Value v : values) {
cell.values.add(v); // values are immutable, so we don't need to clone them
}
return cell;
}
}

View File

@ -0,0 +1,62 @@
package org.hl7.fhir.r5.utils.sql;
public class Column {
private String name;
private int length;
private String type;
private ColumnKind kind;
private boolean isColl;
protected Column() {
super();
}
protected Column(String name, boolean isColl, String type, ColumnKind kind) {
super();
this.name = name;
this.isColl = isColl;
this.type = type;
this.kind = kind;
}
public String getName() {
return name;
}
public int getLength() {
return length;
}
public ColumnKind getKind() {
return kind;
}
public void setName(String name) {
this.name = name;
}
public void setLength(int length) {
this.length = length;
}
public void setKind(ColumnKind kind) {
this.kind = kind;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isColl() {
return isColl;
}
public void setColl(boolean isColl) {
this.isColl = isColl;
}
}

View File

@ -0,0 +1,5 @@
package org.hl7.fhir.r5.utils.sql;
public enum ColumnKind {
String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex
}

View File

@ -0,0 +1,11 @@
package org.hl7.fhir.r5.utils.sql;
import java.util.List;
import org.hl7.fhir.r5.model.Base;
public interface Provider {
List<Base> fetch(String resourceType);
Base resolveReference(String ref, String resourceType);
}

View File

@ -0,0 +1,431 @@
package org.hl7.fhir.r5.utils.sql;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.net.util.Base64;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Base64BinaryType;
import org.hl7.fhir.r5.model.BaseDateTimeType;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.DecimalType;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails;
import org.hl7.fhir.utilities.json.model.JsonObject;
public class Runner implements IEvaluationContext {
private IWorkerContext context;
private Provider provider;
private Storage storage;
private List<String> prohibitedNames = new ArrayList<String>();
private FHIRPathEngine fpe;
private List<Column> columns = new ArrayList<>();
private String resourceName;
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<String> getProhibitedNames() {
return prohibitedNames;
}
public void execute(JsonObject viewDefinition) {
execute("$", viewDefinition);
}
public void execute(String path, JsonObject viewDefinition) {
if (context == null) {
throw new FHIRException("No context provided");
}
fpe = new FHIRPathEngine(context);
fpe.setHostServices(this);
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);
validator.dump();
validator.check();
resourceName = validator.getResourceName();
columns = validator.getColumns();
evaluate(viewDefinition);
}
private void evaluate(JsonObject vd) {
Store store = storage.createStore(vd.asString("name"), columns);
List<Base> data = provider.fetch(resourceName);
for (Base b : data) {
boolean ok = true;
for (JsonObject w : vd.getJsonObjects("where")) {
String expr = w.asString("path");
ExpressionNode node = fpe.parse(expr);
boolean pass = fpe.evaluateToBoolean(null, b, b, b, node);
if (!pass) {
ok = false;
break;
}
}
if (ok) {
List<List<Cell>> rows = new ArrayList<>();
generateCells(b, vd, rows);
for (List<Cell> row : rows) {
storage.addRow(store, row);
}
}
}
storage.finish(store);
}
private void generateCells(Base bl, JsonObject vd, List<List<Cell>> rows) {
if (vd.has("forEach")) {
executeForEach(vd, bl, rows);
} else if (vd.has("forEachOrNull")) {
executeForEachOrNull(vd, bl, rows);
} else if (vd.has("union")) {
executeUnion(vd, bl, rows);
} else {
for (JsonObject select : vd.getJsonObjects("select")) {
executeSelect(select, bl, rows);
}
}
}
private void executeSelect(JsonObject select, Base bl, List<List<Cell>> rows) {
if (select.has("path")) {
executeSelectPath(select, bl, rows);
} else if (select.has("forEach")) {
executeForEach(select, bl, rows);
} else if (select.has("forEachOrNull")) {
executeForEachOrNull(select, bl, rows);
} else if (select.has("union")) {
executeUnion(select, bl, rows);
}
}
private void executeForEach(JsonObject focus, Base b, List<List<Cell>> rows) {
ExpressionNode n = (ExpressionNode) focus.getUserData("forEach");
List<Base> bl2 = fpe.evaluate(b, n);
List<List<Cell>> tempRows = new ArrayList<>();
tempRows.addAll(rows);
rows.clear();
for (Base b2 : bl2) {
List<List<Cell>> rowsToAdd = cloneRows(tempRows);
for (JsonObject select : focus.getJsonObjects("select")) {
executeSelect(select, b2, rowsToAdd);
}
rows.addAll(rowsToAdd);
}
}
private List<List<Cell>> cloneRows(List<List<Cell>> rows) {
List<List<Cell>> list = new ArrayList<>();
for (List<Cell> row : rows) {
list.add(cloneRow(row));
}
return list;
}
private List<Cell> cloneRow(List<Cell> cells) {
List<Cell> list = new ArrayList<>();
for (Cell cell : cells) {
list.add(cell.copy());
}
return list;
}
private void executeForEachOrNull(JsonObject focus, Base b, List<List<Cell>> rows) {
throw new FHIRException("forEachOrNull is not supported");
}
private void executeUnion(JsonObject focus, Base b, List<List<Cell>> rows) {
throw new FHIRException("union is not supported");
}
private void executeSelectPath(JsonObject select, Base b, List<List<Cell>> rows) {
ExpressionNode n = (ExpressionNode) select.getUserData("path");
List<Base> bl2 = fpe.evaluate(b, n);
String name = select.getUserString("name");
if (!bl2.isEmpty()) {
if (rows.isEmpty()) {
rows.add(new ArrayList<Cell>());
}
for (List<Cell> row : rows) {
Cell c = cell(row, name);
if (c == null) {
c = new Cell(column(name));
row.add(c);
}
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));
}
}
} else {
for (List<Cell> row : rows) {
Cell c = cell(row, name);
if (c == null) {
c = new Cell(column(name));
row.add(c);
}
}
}
}
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) {
for (Column t : columns) {
if (t.getName().equalsIgnoreCase(columnName)) {
return t;
}
}
return null;
}
private Cell cell(List<Cell> cells, String columnName) {
for (Cell t : cells) {
if (t.getColumn().getName().equalsIgnoreCase(columnName)) {
return t;
}
}
return null;
}
@Override
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
throw new Error("Not implemented yet: resolveConstant");
}
@Override
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
throw new Error("Not implemented yet: resolveConstantType");
}
@Override
public boolean log(String argument, List<Base> focus) {
throw new Error("Not implemented yet: log");
}
@Override
public FunctionDetails resolveFunction(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(Object appContext, String functionName, List<TypeDetails> 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<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
switch (functionName) {
case "getResourceKey" : return executeResourceKey(focus);
case "getReferenceKey" : return executeReferenceKey(focus, parameters);
default: throw new Error("Not known: "+functionName);
}
}
private List<Base> executeResourceKey(List<Base> focus) {
List<Base> base = new ArrayList<Base>();
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<Base> executeReferenceKey(List<Base> focus, List<List<Base>> parameters) {
String rt = null;
if (parameters.size() > 0) {
rt = parameters.get(0).get(0).primitiveValue();
}
List<Base> base = new ArrayList<Base>();
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(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(Object appContext, String url, Base refContext) throws FHIRException {
throw new Error("Not implemented yet: resolveReference");
}
@Override
public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
throw new Error("Not implemented yet: conformsToProfile");
}
@Override
public ValueSet resolveValueSet(Object appContext, String url) {
throw new Error("Not implemented yet: resolveValueSet");
}
}

View File

@ -0,0 +1,18 @@
package org.hl7.fhir.r5.utils.sql;
import java.util.List;
import org.hl7.fhir.r5.model.Base;
public interface Storage {
boolean supportsArrays();
boolean supportsComplexTypes();
Store createStore(String name, List<Column> columns);
void addRow(Store store, List<Cell> cells);
void finish(Store store);
boolean needsName();
String getKeyForSourceResource(Base res);
String getKeyForTargetResource(Base res);
}

View File

@ -0,0 +1,96 @@
package org.hl7.fhir.r5.utils.sql;
import java.util.List;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonBoolean;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNull;
import org.hl7.fhir.utilities.json.model.JsonNumber;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.model.JsonString;
public class StorageJson implements Storage {
private String name;
private JsonArray rows;
@Override
public boolean supportsArrays() {
return true;
}
@Override
public Store createStore(String name, List<Column> columns) {
this.name = name;
this.rows = new JsonArray();
return new Store(name); // we're not doing anything with this
}
@Override
public void addRow(Store store, List<Cell> cells) {
JsonObject row = new JsonObject();
rows.add(row);
for (Cell cell : cells) {
if (cell.getValues().size() == 0) {
row.add(cell.getColumn().getName(), new JsonNull());
} else if (cell.getValues().size() == 1) {
row.add(cell.getColumn().getName(), makeJsonNode(cell.getValues().get(0)));
} else {
JsonArray arr = new JsonArray();
row.add(cell.getColumn().getName(), arr);
for (Value value : cell.getValues()) {
arr.add(makeJsonNode(value));
}
}
}
}
private JsonElement makeJsonNode(Value value) {
if (value.getValueInt() != null) {
return new JsonNumber(value.getValueInt().intValue());
}
if (value.getValueBoolean() != null) {
return new JsonBoolean(value.getValueBoolean().booleanValue());
}
if (value.getValueDecimal() != null) {
return new JsonNumber(value.getValueDecimal().toPlainString());
}
return new JsonString(value.getValueString());
}
@Override
public void finish(Store store) {
// nothing
}
public String getName() {
return name;
}
public JsonArray getRows() {
return rows;
}
@Override
public boolean supportsComplexTypes() {
return true;
}
@Override
public boolean needsName() {
return false;
}
@Override
public String getKeyForSourceResource(Base res) {
return res.getIdBase();
}
@Override
public String getKeyForTargetResource(Base res) {
return res.fhirType()+"/"+res.getIdBase();
}
}

View File

@ -0,0 +1,141 @@
package org.hl7.fhir.r5.utils.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
public class StorageSqlite3 implements Storage {
public static class SQLiteStore extends Store {
private PreparedStatement p;
protected SQLiteStore(String name, PreparedStatement p) {
super(name);
this.p = p;
}
public PreparedStatement getP() {
return p;
}
}
private Connection conn;
private int nextKey = 0;
protected StorageSqlite3(Connection conn) {
super();
this.conn = conn;
}
@Override
public Store createStore(String name, List<Column> columns) {
try {
CommaSeparatedStringBuilder fields = new CommaSeparatedStringBuilder(", ");
CommaSeparatedStringBuilder values = new CommaSeparatedStringBuilder(", ");
StringBuilder b = new StringBuilder();
b.append("Create Table "+name+" { ");
b.append("ViewRowKey integer NOT NULL");
for (Column column : columns) {
b.append(", "+column.getName()+" "+sqliteType(column.getKind())+" NULL"); // index columns are always nullable
fields.append(column.getName());
values.append("?");
}
b.append(", PRIMARY KEY (ViewRowKey))\r\n");
conn.createStatement().execute(b.toString());
String isql = "Insert into "+name+" ("+fields.toString()+") values ("+values.toString()+")";
PreparedStatement psql = conn.prepareStatement(isql);
return new SQLiteStore(name, psql);
} catch (Exception e) {
throw new FHIRException(e);
}
}
private String sqliteType(ColumnKind type) {
switch (type) {
case DateTime: return "Text";
case Decimal: return "Real";
case Integer: return "Integer";
case String: return "Text";
case Time: return "Text";
case Binary: return "Text";
case Boolean: return "Integer";
case Complex: throw new FHIRException("SQLite runner does not handle complexes");
}
return null;
}
@Override
public void addRow(Store store, List<Cell> cells) {
try {
SQLiteStore sqls = (SQLiteStore) store;
PreparedStatement p = sqls.getP();
p.setInt(1, ++nextKey);
for (int i = 0; i < cells.size(); i++) {
Cell c = cells.get(i);
switch (c.getColumn().getKind()) {
case Binary:
p.setBytes(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueBinary());
break;
case Boolean:
p.setBoolean(i+2, c.getValues().size() == 0 ? false : c.getValues().get(0).getValueBoolean().booleanValue());
break;
case DateTime:
p.setDate(i+2, c.getValues().size() == 0 ? null : new java.sql.Date(c.getValues().get(0).getValueDate().getTime()));
break;
case Decimal:
p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString());
break;
case Integer:
p.setInt(i+2, c.getValues().size() == 0 ? 0 : c.getValues().get(0).getValueInt().intValue());
break;
case String:
p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString());
break;
case Time:
p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString());
break;
case Complex: throw new FHIRException("SQLite runner does not handle complexes");
}
}
} catch (Exception e) {
throw new FHIRException(e);
}
}
@Override
public void finish(Store store) {
// nothing
}
@Override
public boolean supportsArrays() {
return false;
}
@Override
public boolean supportsComplexTypes() {
return false;
}
@Override
public boolean needsName() {
return true;
}
@Override
public String getKeyForSourceResource(Base res) {
throw new Error("Key management for resources isn't decided yet");
}
@Override
public String getKeyForTargetResource(Base res) {
throw new Error("Key management for resources isn't decided yet");
}
}

View File

@ -0,0 +1,16 @@
package org.hl7.fhir.r5.utils.sql;
public class Store {
private String name;
protected Store(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,507 @@
package org.hl7.fhir.r5.utils.sql;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonBoolean;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNumber;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.model.JsonString;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public class Validator {
private IWorkerContext context;
private FHIRPathEngine fpe;
private List<String> prohibitedNames = new ArrayList<String>();
private List<ValidationMessage> issues = new ArrayList<ValidationMessage>();
private boolean arrays;
private boolean complexTypes;
private boolean needsName;
private String resourceName;
private List<Column> columns = new ArrayList<Column>();
private String name;
protected Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, boolean arrays, boolean complexTypes, boolean needsName) {
super();
this.context = context;
this.fpe = fpe;
this.prohibitedNames = prohibitedNames;
this.arrays = arrays;
this.complexTypes = complexTypes;
this.needsName = needsName;
}
public String getResourceName() {
return resourceName;
}
public List<Column> getColumns() {
return columns;
}
public void checkViewDefinition(String path, JsonObject viewDefinition) {
JsonElement nameJ = viewDefinition.get("name");
if (nameJ == null) {
if (needsName) {
error(path, viewDefinition, "No name provided", IssueType.REQUIRED);
}
} else if (!(nameJ instanceof JsonString)) {
error(path, viewDefinition, "name must be a string", IssueType.INVALID);
} else {
name = nameJ.asString();
if (!isValidName(name)) {
error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT);
}
if (prohibitedNames.contains(name)) {
error(path, nameJ, "The name '"+name+"' on the viewDefinition is not allowed in this context", IssueType.BUSINESSRULE);
}
}
JsonElement resourceNameJ = viewDefinition.get("resource");
if (resourceNameJ == null) {
error(path, viewDefinition, "No resource provided", IssueType.REQUIRED);
} else if (!(resourceNameJ instanceof JsonString)) {
error(path, viewDefinition, "resource must be a string", IssueType.INVALID);
} else {
resourceName = resourceNameJ.asString();
if (!context.getResourceNamesAsSet().contains(resourceName)) {
error(path+".name", nameJ, "The name '"+resourceName+"' is not a valid resource", IssueType.BUSINESSRULE);
} else {
int i = 0;
if (checkAllObjects(path, viewDefinition, "constant")) {
for (JsonObject constant : viewDefinition.getJsonObjects("constant")) {
checkConstant(path+".constant["+i+"]", constant);
i++;
}
}
i = 0;
if (checkAllObjects(path, viewDefinition, "where")) {
for (JsonObject where : viewDefinition.getJsonObjects("where")) {
checkWhere(path+".where["+i+"]", where);
i++;
}
}
TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName);
if (viewDefinition.has("forEach")) {
checkForEach(path, viewDefinition, viewDefinition.get("forEach"), t);
} else if (viewDefinition.has("forEachOrNull")) {
checkForEachOrNull(path, viewDefinition, viewDefinition.get("forEachOrNull"), t);
} else if (viewDefinition.has("union")) {
checkUnion(path, viewDefinition, viewDefinition.get("union"), t);
} else {
i = 0;
if (checkAllObjects(path, viewDefinition, "select")) {
for (JsonObject select : viewDefinition.getJsonObjects("select")) {
checkSelect(path+".select["+i+"]", select, t);
i++;
}
if (i == 0) {
error(path, viewDefinition, "No select statements found", IssueType.REQUIRED);
}
}
}
}
}
}
private void checkSelect(String path, JsonObject select, TypeDetails t) {
if (select.has("path")) {
checkSelectPath(path, select, select.get("path"), t);
} else if (select.has("forEach")) {
checkForEach(path, select, select.get("forEach"), t);
} else if (select.has("forEachOrNull")) {
checkForEachOrNull(path, select, select.get("forEachOrNull"), t);
} else if (select.has("union")) {
checkUnion(path, select, select.get("union"), t);
} else {
error(path, select, "The select has neither a path, forEach, forEachOrNull, or union statement", IssueType.REQUIRED);
}
}
private void checkSelectPath(String path, JsonObject select, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
} else {
String expr = expression.asString();
List<String> warnings = new ArrayList<>();
TypeDetails td = null;
ExpressionNode node = null;
try {
node = fpe.parse(expr);
select.setUserData("path", node);
td = fpe.checkOnTypes(null, resourceName, t, node, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null && node != null) {
for (String s : warnings) {
warning(path+".path", expression, s);
}
String columnName = null;
JsonElement aliasJ = select.get("alias");
if (aliasJ != null) {
if (aliasJ instanceof JsonString) {
columnName = aliasJ.asString();
if (!isValidName(columnName)) {
error(path+".name", aliasJ, "The name '"+columnName+"' is not valid", IssueType.VALUE);
}
} else {
error(path+".alias", aliasJ, "alias must be a string", IssueType.INVALID);
}
}
if (columnName == null) {
List<String> names = node.getDistalNames();
if (names.size() == 1 && names.get(0) != null) {
columnName = names.get(0);
if (!isValidName(columnName)) {
error(path+".path", expression, "The name '"+columnName+"' found in the path expression is not a valid column name, so an alias is required", IssueType.INVARIANT);
}
} else {
error(path, select, "The path does not resolve to a name, so an alias is required", IssueType.REQUIRED);
}
}
// ok, name is sorted!
if (columnName != null) {
select.setUserData("name", columnName);
boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON) || column(columnName) != null;
if (select.has("collection")) {
JsonElement collectionJ = select.get("collection");
if (!(collectionJ instanceof JsonBoolean)) {
error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID);
} else {
boolean collection = collectionJ.asJsonBoolean().asBoolean();
if (!collection && isColl) {
isColl = false;
warning(path, select, "collection is false, but the path statement(s) might return multiple values for the column '"+columnName+"' some inputs");
}
}
}
if (isColl && !arrays) {
warning(path, expression, "column appears to be a collection, but this is not allowed in this context");
}
// ok collection is sorted
Set<String> types = new HashSet<>();
for (String type : td.getTypes()) {
types.add(simpleType(type));
}
JsonElement typeJ = select.get("type");
if (typeJ != null) {
if (typeJ instanceof JsonString) {
String type = typeJ.asString();
if (!td.hasType(type)) {
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type, IssueType.VALUE);
} else {
types.clear();
types.add(simpleType(type));
}
} else {
error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
}
}
if (types.size() != 1) {
error(path, select, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE);
} else {
String type = types.iterator().next();
if (!isSimpleType(type) && !complexTypes) {
error(path, expression, "column is a complex type but this is not allowed in this context", IssueType.BUSINESSRULE);
} else {
Column col = column(columnName);
if (col != null) {
if (!col.getType().equals(type)) {
error(path, expression, "Duplicate definition for "+columnName+" has different types ("+col.getType()+" vs "+type+")", IssueType.BUSINESSRULE);
}
if (col.isColl() != isColl) {
error(path, expression, "Duplicate definition for "+columnName+" has different status for collection ("+col.isColl()+" vs "+isColl+")", IssueType.BUSINESSRULE);
}
} else {
columns.add(new Column(columnName, isColl, type, kindForType(type)));
}
}
}
}
}
}
}
private ColumnKind kindForType(String type) {
switch (type) {
case "dateTime": return ColumnKind.DateTime;
case "boolean": return ColumnKind.Boolean;
case "integer": return ColumnKind.Integer;
case "decimal": return ColumnKind.Decimal;
case "string": return ColumnKind.String;
case "base64Binary": return ColumnKind.Binary;
case "time": return ColumnKind.Time;
default: return ColumnKind.Complex;
}
}
private Column column(String columnName) {
for (Column t : columns) {
if (t.getName().equalsIgnoreCase(columnName)) {
return t;
}
}
return null;
}
private boolean isSimpleType(String type) {
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary");
}
private String simpleType(String type) {
type = type.replace("http://hl7.org/fhirpath/System.", "").replace("http://hl7.org/fhir/StructureDefinition/", "");
if (Utilities.existsInList(type, "date", "dateTime", "instant")) {
return "dateTime";
}
if (Utilities.existsInList(type, "Boolean", "boolean")) {
return "boolean";
}
if (Utilities.existsInList(type, "Integer", "integer", "integer64")) {
return "integer";
}
if (Utilities.existsInList(type, "Decimal", "decimal")) {
return "decimal";
}
if (Utilities.existsInList(type, "String", "string", "code")) {
return "string";
}
if (Utilities.existsInList(type, "Time", "time")) {
return "time";
}
if (Utilities.existsInList(type, "base64Binary")) {
return "base64Binary";
}
return type;
}
private void checkForEach(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
} else {
String expr = expression.asString();
List<String> warnings = new ArrayList<>();
TypeDetails td = null;
try {
ExpressionNode n = fpe.parse(expr);
focus.setUserData("forEach", n);
td = fpe.checkOnTypes(null, resourceName, t, n, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null) {
for (String s : warnings) {
warning(path+".path", expression, s);
}
int i = 0;
if (checkAllObjects(path, focus, "select")) {
for (JsonObject select : focus.getJsonObjects("select")) {
checkSelect(path+".select["+i+"]", select, td);
i++;
}
if (i == 0) {
error(path, focus, "No select statements found", IssueType.REQUIRED);
}
}
}
}
}
private void checkForEachOrNull(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
error(path+".forEachOrNull", expression, "forEachOrNull is not supported", IssueType.BUSINESSRULE);
}
private void checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) {
error(path+".union", focus.get("union"), "union is not supported", IssueType.BUSINESSRULE);
}
private void checkConstant(String path, JsonObject constant) {
JsonElement nameJ = constant.get("name");
if (nameJ == null) {
error(path, constant, "No name provided", IssueType.REQUIRED);
} else if (!(nameJ instanceof JsonString)) {
error(path, constant, "Name must be a string", IssueType.INVALID);
} else {
String name = constant.asString("name");
if (!isValidName(name)) {
error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT);
}
}
if (constant.has("valueBase64Binary")) {
checkIsString(path, constant, "valueBase64Binary");
} else if (constant.has("valueBoolean")) {
checkIsBoolean(path, constant, "valueBoolean");
} else if (constant.has("valueCanonical")) {
checkIsString(path, constant, "valueCanonical");
} else if (constant.has("valueCode")) {
checkIsString(path, constant, "valueCode");
} else if (constant.has("valueDate")) {
checkIsString(path, constant, "valueDate");
} else if (constant.has("valueDateTime")) {
checkIsString(path, constant, "valueDateTime");
} else if (constant.has("valueDecimal")) {
checkIsNumber(path, constant, "valueDecimal");
} else if (constant.has("valueId")) {
checkIsString(path, constant, "valueId");
} else if (constant.has("valueInstant")) {
checkIsString(path, constant, "valueInstant");
} else if (constant.has("valueInteger")) {
checkIsNumber(path, constant, "valueInteger");
} else if (constant.has("valueInteger64")) {
checkIsNumber(path, constant, "valueInteger64");
} else if (constant.has("valueOid")) {
checkIsString(path, constant, "valueOid");
} else if (constant.has("valueString")) {
checkIsString(path, constant, "valueString");
} else if (constant.has("valuePositiveInt")) {
checkIsNumber(path, constant, "valuePositiveInt");
} else if (constant.has("valueTime")) {
checkIsString(path, constant, "valueTime");
} else if (constant.has("valueUnsignedInt")) {
checkIsNumber(path, constant, "valueUnsignedInt");
} else if (constant.has("valueUri")) {
checkIsString(path, constant, "valueUri");
} else if (constant.has("valueUrl")) {
checkIsString(path, constant, "valueUrl");
} else if (constant.has("valueUuid")) {
checkIsString(path, constant, "valueUuid");
} else {
error(path, constant, "No value found", IssueType.REQUIRED);
}
}
private void checkIsString(String path, JsonObject constant, String name) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonString)) {
error(path+"."+name, j, name+" must be a string", IssueType.INVALID);
}
}
private void checkIsBoolean(String path, JsonObject constant, String name) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonBoolean)) {
error(path+"."+name, j, name+" must be a boolean", IssueType.INVALID);
}
}
private void checkIsNumber(String path, JsonObject constant, String name) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonNumber)) {
error(path+"."+name, j, name+" must be a number", IssueType.INVALID);
}
}
private void checkWhere(String path, JsonObject where) {
String expr = where.asString("path");
if (expr == null) {
error(path, where, "No path provided", IssueType.REQUIRED);
}
List<String> types = new ArrayList<>();
List<String> warnings = new ArrayList<>();
types.add(resourceName);
TypeDetails td = null;
try {
ExpressionNode n = fpe.parse(expr);
where.setUserData("path", n);
td = fpe.checkOnTypes(null, resourceName, types, n, warnings);
} catch (Exception e) {
error(path, where.get("path"), e.getMessage(), IssueType.INVALID);
}
if (td != null) {
if (td.getCollectionStatus() != CollectionStatus.SINGLETON || td.getTypes().size() != 1 || !td.hasType("boolean")) {
error(path+".path", where.get("path"), "A where path must return a boolean, but the expression "+expr+" returns a "+td.describe(), IssueType.BUSINESSRULE);
} else {
for (String s : warnings) {
warning(path+".path", where.get("path"), s);
}
}
}
}
private boolean isValidName(String name) {
boolean first = true;
for (char c : name.toCharArray()) {
if (!(Character.isAlphabetic(c) || Character.isDigit(c) || (!first && c == '_'))) {
return false;
}
first = false;
}
return true;
}
private boolean checkAllObjects(String path, JsonObject focus, String name) {
if (!focus.has(name)) {
return true;
} else if (!(focus.get(name) instanceof JsonArray)) {
error(path+"."+name, focus.get(name), name+" must be an array", IssueType.INVALID);
return false;
} else {
JsonArray arr = focus.getJsonArray(name);
int i = 0;
boolean ok = true;
for (JsonElement e : arr) {
if (!(e instanceof JsonObject)) {
error(path+"."+name+"["+i+"]", e, name+"["+i+"] must be an object", IssueType.INVALID);
ok = false;
}
}
return ok;
}
}
private void error(String path, JsonElement e, String issue, IssueType type) {
ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, type, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.ERROR);
issues.add(vm);
}
private void warning(String path, JsonElement e, String issue) {
ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.WARNING);
issues.add(vm);
}
public void dump() {
for (ValidationMessage vm : issues) {
System.out.println(vm.summary());
}
}
public void check() {
boolean ok = true;
for (ValidationMessage vm : issues) {
if (vm.isError()) {
ok = false;
}
}
if (!ok) {
throw new FHIRException("View Definition is not valid");
}
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,129 @@
package org.hl7.fhir.r5.utils.sql;
import java.math.BigDecimal;
import java.util.Date;
import org.hl7.fhir.r5.model.Base;
/**
* String value is always provided, and a more specific value may also be provided
*/
public class Value {
private String valueString;
private Boolean valueBoolean;
private Date valueDate;
private Integer valueInt;
private BigDecimal valueDecimal;
private byte[] valueBinary;
private Base valueComplex;
private Value() {
super();
}
public static Value makeString(String s) {
Value v = new Value();
v.valueString = s;
return v;
}
public static Value makeBoolean(String s, Boolean b) {
Value v = new Value();
v.valueString = s;
v.valueBoolean = b;
return v;
}
public static Value makeDate(String s, Date d) {
Value v = new Value();
v.valueString = s;
v.valueDate = d;
return v;
}
public static Value makeInteger(String s, Integer i) {
Value v = new Value();
v.valueString = s;
v.valueInt = i;
return v;
}
public static Value makeDecimal(String s, BigDecimal bigDecimal) {
Value v = new Value();
v.valueString = s;
v.valueDecimal = bigDecimal;
return v;
}
public static Value makeBinary(String s, byte[] b) {
Value v = new Value();
v.valueString = s;
v.valueBinary = b;
return v;
}
public static Value makeComplex(Base b) {
Value v = new Value();
v.valueComplex = b;
return v;
}
public String getValueString() {
return valueString;
}
public Date getValueDate() {
return valueDate;
}
public Integer getValueInt() {
return valueInt;
}
public BigDecimal getValueDecimal() {
return valueDecimal;
}
public byte[] getValueBinary() {
return valueBinary;
}
public Boolean getValueBoolean() {
return valueBoolean;
}
public Base getValueComplex() {
return valueComplex;
}
public boolean hasValueString() {
return valueString != null;
}
public boolean hasValueDate() {
return valueDate != null;
}
public boolean hasValueInt() {
return valueInt != null;
}
public boolean hasValueDecimal() {
return valueDecimal != null;
}
public boolean hasValueBinary() {
return valueBinary != null;
}
public boolean hasValueBoolean() {
return valueBoolean != null;
}
public boolean hasValueComplex() {
return valueComplex != null;
}
}

View File

@ -0,0 +1,169 @@
package org.hl7.fhir.r5.sql;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import javax.xml.parsers.ParserConfigurationException;
import org.fhir.ucum.UcumException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Factory;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ResourceFactory;
import org.hl7.fhir.r5.sql.SQLOnFhirTestCases.RowSorter;
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.sql.Provider;
import org.hl7.fhir.r5.utils.sql.Runner;
import org.hl7.fhir.r5.utils.sql.StorageJson;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.xml.sax.SAXException;
public class SQLOnFhirTestCases {
public class TestProvider implements Provider {
@Override
public List<Base> fetch(String resourceType) {
List<Base> list = new ArrayList<Base>();
for (JsonObject res : details.resources) {
try {
String src = JsonParser.compose(res, false);
Resource resource = new org.hl7.fhir.r5.formats.JsonParser().parse(src);
if (resource.fhirType().equals(resourceType)) {
list.add(resource);
}
} catch (Exception e) {
throw new FHIRException(e);
}
}
return list;
}
@Override
public Base resolveReference(String ref, String rt) {
if (ref == null) {
return null;
}
String[] p = ref.split("\\/");
if (p.length > 1 && TestingUtilities.getSharedWorkerContext().getResourceNamesAsSet().contains(p[p.length-2])) {
if (rt == null || rt.equals(p[p.length-2])) {
return ResourceFactory.createResource(p[p.length-2]).setId(p[p.length-1]);
}
}
return null;
}
}
private static class TestDetails {
String name;
String path;
List<JsonObject> resources;
JsonObject testCase;
protected TestDetails(String name, String path, List<JsonObject> resources, JsonObject testCase) {
super();
this.name = name;
this.path = path;
this.resources = resources;
this.testCase = testCase;
}
}
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
List<Arguments> objects = new ArrayList<>();
File dir = new File("/Users/grahamegrieve/work/sql-on-fhir-v2/tests/content");
for (File f : dir.listFiles()) {
if (f.getName().endsWith(".json")) {
JsonObject json = JsonParser.parseObject(f);
String name1 = f.getName().replace(".json", "");
List<JsonObject> resources = json.getJsonObjects("resources");
int i = 0;
for (JsonObject test : json.getJsonObjects("tests")) {
String name2 = test.asString("title");
objects.add(Arguments.of(name1+":"+name2, new TestDetails(name1+":"+name2, "$.tests["+i+"]", resources, test)));
i++;
}
}
}
return objects.stream();
}
private TestDetails details;
@SuppressWarnings("deprecation")
@ParameterizedTest(name = "{index}: file {0}")
@MethodSource("data")
public void test(String name, TestDetails test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
this.details = test;
Runner runner = new Runner();
runner.setContext(TestingUtilities.getSharedWorkerContext());
runner.setProvider(new TestProvider());
StorageJson store = new StorageJson();
runner.setStorage(store);
System.out.println("");
System.out.println("----------------------------------------------------------------------------");
System.out.println(test.name);
JsonArray results = null;
try {
runner.execute(test.path+".view", test.testCase.getJsonObject("view"));
results = store.getRows();
} catch (Exception e) {
Assertions.assertTrue(test.testCase.has("expectError"), e.getMessage());
}
if (results != null) {
System.out.println(JsonParser.compose(results, true));
if (test.testCase.has("expect")) {
JsonObject rows = new JsonObject();
rows.add("rows", results);
JsonObject exp = new JsonObject();
exp.add("rows", test.testCase.getJsonArray("expect"));
sortResults(exp);
sortResults(rows);
String expS = JsonParser.compose(exp, true);
String rowS = JsonParser.compose(rows, true);
String c = CompareUtilities.checkJsonSrcIsSame(expS, rowS, null);
Assertions.assertNull(c, c);
} else if (test.testCase.has("expectCount")) {
Assertions.assertEquals(test.testCase.asInteger("expectCount"), results.size());
} else {
Assertions.fail("?");
}
}
}
public class RowSorter implements Comparator<JsonElement> {
@Override
public int compare(JsonElement e1, JsonElement e2) {
String s1 = JsonParser.compose(e1);
String s2 = JsonParser.compose(e2);
return s1.compareTo(s2);
}
}
private void sortResults(JsonObject o) {
Collections.sort(o.getJsonArray("rows").getItems(), new RowSorter());
}
}

View File

@ -117,7 +117,9 @@ public class CommaSeparatedStringBuilder {
public static String join(String sep, Collection<String> list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(sep);
for (String s : list) {
b.append(s);
if (s != null) {
b.append(s);
}
}
return b.toString();
}

View File

@ -1,7 +1,9 @@
package org.hl7.fhir.utilities.json.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.utilities.json.JsonException;
@ -10,6 +12,7 @@ public abstract class JsonElement {
private List<JsonComment> comments;
private JsonLocationData start;
private JsonLocationData end;
private Map<String, Object> userData;
public abstract JsonElementType type();
@ -112,4 +115,66 @@ public abstract class JsonElement {
public String asString() {
return isJsonPrimitive() ? ((JsonPrimitive) this).getValue() : null;
}
public Object getUserData(String name) {
if (userData == null)
return null;
return userData.get(name);
}
public void setUserData(String name, Object value) {
if (userData == null)
userData = new HashMap<String, Object>();
userData.put(name, value);
}
public void clearUserData(String name) {
if (userData != null)
userData.remove(name);
}
public void setUserDataINN(String name, Object value) {
if (value == null)
return;
if (userData == null)
userData = new HashMap<String, Object>();
userData.put(name, value);
}
public boolean hasUserData(String name) {
if (userData == null)
return false;
else
return userData.containsKey(name) && (userData.get(name) != null);
}
public String getUserString(String name) {
Object ud = getUserData(name);
if (ud == null)
return null;
if (ud instanceof String)
return (String) ud;
return ud.toString();
}
public int getUserInt(String name) {
if (!hasUserData(name))
return 0;
return (Integer) getUserData(name);
}
public void copyUserData(JsonElement other) {
if (other.userData != null) {
if (userData == null) {
userData = new HashMap<>();
}
userData.putAll(other.userData);
}
}
}