Merge pull request #1449 from hapifhir/2023-10-gg-xig_steps
2023 10 gg xig steps
This commit is contained in:
commit
d6eb345692
|
@ -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,6 +165,7 @@ public class PackageVisitor {
|
|||
if (pid != null) {
|
||||
if (!cpidSet.contains(pid)) {
|
||||
cpidSet.add(pid);
|
||||
if (step == 0 || step == 3) {
|
||||
List<String> vList = listVersions(pid);
|
||||
if (oldVersions) {
|
||||
for (String v : vList) {
|
||||
|
@ -174,12 +176,15 @@ public class PackageVisitor {
|
|||
} else {
|
||||
processPackage(pid, vList.get(vList.size() - 1), i, pidList.size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
processor.alreadyVisited(pid);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -201,9 +206,12 @@ public class PackageVisitor {
|
|||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processCurrentPackage(String url, String pid, Set<String> cpidSet, int i, int t) {
|
||||
try {
|
||||
cpidSet.add(pid);
|
||||
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];
|
||||
|
@ -217,7 +225,6 @@ public class PackageVisitor {
|
|||
}
|
||||
NpmPackage npm = NpmPackage.fromPackage(new FileInputStream(co));
|
||||
String fv = npm.fhirVersion();
|
||||
cpidSet.add(pid);
|
||||
long ms2 = System.currentTimeMillis();
|
||||
|
||||
if (corePackages || !corePackage(npm)) {
|
||||
|
@ -252,6 +259,7 @@ public class PackageVisitor {
|
|||
System.out.println("Ignored: "+pid+"#current: no version");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Unable to process: "+pid+"#current: "+e.getMessage());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -712,4 +712,22 @@ public class ExpressionNode {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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,13 +4216,17 @@ 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;
|
||||
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>();
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.hl7.fhir.r5.utils.sql;
|
||||
|
||||
public enum ColumnKind {
|
||||
String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -117,8 +117,10 @@ public class CommaSeparatedStringBuilder {
|
|||
public static String join(String sep, Collection<String> list) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(sep);
|
||||
for (String s : list) {
|
||||
if (s != null) {
|
||||
b.append(s);
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue