mirror of
https://github.com/hapifhir/org.hl7.fhir.core.git
synced 2025-03-06 03:19:15 +00:00
Test Instance Generation, and many fixes to PE model
This commit is contained in:
parent
dbca663300
commit
36a23c4a21
@ -19,6 +19,7 @@ import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage.PackagedResourceFile;
|
||||
import org.hl7.fhir.utilities.npm.PackageClient;
|
||||
import org.hl7.fhir.utilities.npm.PackageInfo;
|
||||
import org.hl7.fhir.utilities.npm.PackageServer;
|
||||
@ -357,6 +358,7 @@ public class PackageVisitor {
|
||||
npm = pcm.loadPackage(pid, v);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("Unable to load package: "+pid+"#"+v+": "+e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -378,15 +380,13 @@ public class PackageVisitor {
|
||||
if (ok) {
|
||||
int c = 0;
|
||||
if (fv != null && (versions.isEmpty() || versions.contains(fv))) {
|
||||
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+"#"+v +"["+fv+"]/"+type+" ####### "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
for (PackagedResourceFile p : npm.listAllResources(resourceTypes)) {
|
||||
c++;
|
||||
try {
|
||||
processor.processResource(ctxt, context, p.getResourceType(), p.getFilename(), TextFile.streamToBytes(npm.load(p.getFolder(), p.getFilename())));
|
||||
} catch (Exception e) {
|
||||
System.out.println("####### Error loading "+pid+"#"+v +"["+fv+"]/"+p.getResourceType()+" ####### "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,17 @@ import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.hl7.fhir.convertors.analytics.PackageVisitor.IPackageVisitorProcessor;
|
||||
import org.hl7.fhir.convertors.analytics.PackageVisitor.PackageContext;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.ILoaderKnowledgeProviderR5;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.NullLoaderKnowledgeProviderR5;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R2016MayToR5Loader;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R2ToR5Loader;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R3ToR5Loader;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R4BToR5Loader;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R4ToR5Loader;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R5ToR5Loader;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R6ToR5Loader;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IContextResourceLoader;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext.SimpleWorkerContextBuilder;
|
||||
@ -37,6 +47,7 @@ import org.hl7.fhir.utilities.http.ManagedWebAccess;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccess.WebAccessPolicy;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage.PackagedResourceFile;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class TestGenerationDataGenerator implements IPackageVisitorProcessor {
|
||||
@ -67,6 +78,7 @@ public class TestGenerationDataGenerator implements IPackageVisitorProcessor {
|
||||
|
||||
@Override
|
||||
public Object startPackage(PackageContext ctxt) {
|
||||
System.out.println("Process Package "+ctxt.getPid()+"#"+ctxt.getVersion()+" ("+ctxt.getNpm().fhirVersion()+")");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -77,22 +89,45 @@ public class TestGenerationDataGenerator implements IPackageVisitorProcessor {
|
||||
if (worker == null) {
|
||||
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build();
|
||||
NpmPackage npm = pcm.loadPackage(VersionUtilities.packageForVersion(version));
|
||||
SimpleWorkerContext swc = new SimpleWorkerContextBuilder().withAllowLoadingDuplicates(true).fromPackage(npm);
|
||||
IContextResourceLoader loader = makeLoader(ctxt.getNpm().fhirVersion(), new NullLoaderKnowledgeProviderR5());
|
||||
SimpleWorkerContext swc = new SimpleWorkerContextBuilder().withAllowLoadingDuplicates(true).fromPackage(npm, loader, true);
|
||||
contexts.put(version, swc);
|
||||
worker = swc;
|
||||
}
|
||||
List<ValidatedFragment> res = Manager.parse(worker, new ByteArrayInputStream(content), FhirFormat.JSON);
|
||||
if (res.size() > 0) {
|
||||
try {
|
||||
processElement(res.get(0).getElement());
|
||||
processElement(res.get(0).getElement(), null);
|
||||
} catch (SQLException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processElement(Element element) throws SQLException {
|
||||
String id = element.getProperty().getDefinition().getId();
|
||||
private IContextResourceLoader makeLoader(String version, ILoaderKnowledgeProviderR5 loader) {
|
||||
if (VersionUtilities.isR2Ver(version)) {
|
||||
return new R2ToR5Loader(Utilities.strings("Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader);
|
||||
}
|
||||
if (VersionUtilities.isR2BVer(version)) {
|
||||
return new R2016MayToR5Loader(Utilities.strings("Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader); // special case
|
||||
}
|
||||
if (VersionUtilities.isR3Ver(version)) {
|
||||
return new R3ToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader);
|
||||
}
|
||||
if (VersionUtilities.isR4Ver(version)) {
|
||||
return new R4ToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader, version);
|
||||
}
|
||||
if (VersionUtilities.isR4BVer(version)) {
|
||||
return new R4BToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader, version);
|
||||
}
|
||||
if (VersionUtilities.isR6Ver(version)) {
|
||||
return new R6ToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader);
|
||||
}
|
||||
return new R5ToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"), loader);
|
||||
}
|
||||
|
||||
private void processElement(Element element, String path) throws SQLException {
|
||||
String id = path != null ? path : element.getProperty().getDefinition().getId();
|
||||
switch (element.fhirType()) {
|
||||
case "Address":
|
||||
recordValue(id, "Address", getValues(element, "use", "type", "text", "line", "city", "district", "state", "postalCode", "country"));
|
||||
@ -161,12 +196,23 @@ public class TestGenerationDataGenerator implements IPackageVisitorProcessor {
|
||||
break;
|
||||
default:
|
||||
if (element.isPrimitive()) {
|
||||
recordValue(id, element.fhirType(), element.primitiveValue());
|
||||
if (!element.getProperty().getDefinition().getId().equals("Extension.url")) {
|
||||
recordValue(id, element.fhirType(), element.primitiveValue());
|
||||
}
|
||||
} else {
|
||||
for (Element child : element.getChildren()) {
|
||||
if (!child.fhirType().equals("Extension") && !child.getPath().endsWith(".id") && !child.getPath().endsWith(".linkId")) {
|
||||
if (child.fhirType().equals("Extension")) {
|
||||
String url = child.getChildValue("url");
|
||||
String npath;
|
||||
if (Utilities.isAbsoluteUrl(url) || path == null) {
|
||||
npath = url;
|
||||
} else {
|
||||
npath = path+"."+url;
|
||||
}
|
||||
processElement(child, npath);
|
||||
} else if (!child.getPath().endsWith(".id") && !child.getPath().endsWith(".linkId")) {
|
||||
if (!child.isResource() || !Utilities.existsInList(child.fhirType(), "Bundle", "CapabilityStatement", "CodeSystem", "ConceptMap", "GraphDefinition", "ImplementationGuide", "MessageHeader", "NamingSystem", "OperationDefinition", "OperationOutcome", "Parameters", "SearchParameter", "StructureDefinition", "StructureMap", "TerminologyCapabilities", "ValueSet")) {
|
||||
processElement(child);
|
||||
processElement(child, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -325,9 +371,9 @@ public class TestGenerationDataGenerator implements IPackageVisitorProcessor {
|
||||
for (String type : resourceTypes) {
|
||||
List<String> rt = new ArrayList<String>();
|
||||
rt.add(type);
|
||||
for (StringPair s : npm.listAllResources(rt)) {
|
||||
for (PackagedResourceFile s : npm.listAllResources(rt)) {
|
||||
c++;
|
||||
processResource(ctxt, context, type, s.getName(), TextFile.streamToBytes(npm.load(s.getName(), s.getValue())));
|
||||
processResource(ctxt, context, type, s.getFilename(), TextFile.streamToBytes(npm.load(s.getFolder(), s.getFilename())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ public class PEBuilder {
|
||||
if (!profile.hasSnapshot()) {
|
||||
throw new DefinitionException("Profile '"+profile.getVersionedUrl()+"' does not have a snapshot");
|
||||
}
|
||||
return new PEDefinitionResource(this, profile, profile.getName());
|
||||
return new PEDefinitionResource(this, profile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -342,6 +342,7 @@ public class PEBuilder {
|
||||
|
||||
protected List<PEDefinition> listChildren(boolean allFixed, PEDefinition parent, StructureDefinition profileStructure, ElementDefinition definition, String url, String... omitList) {
|
||||
StructureDefinition profile = profileStructure;
|
||||
boolean inExtension = profile.getDerivation() == TypeDerivationRule.CONSTRAINT && "Extension".equals(profile.getType());
|
||||
List<ElementDefinition> list = pu.getChildList(profile, definition);
|
||||
if (definition.getType().size() == 1 || (!definition.getPath().contains(".")) || list.isEmpty()) {
|
||||
assert url == null || checkType(definition, url);
|
||||
@ -359,43 +360,38 @@ public class PEBuilder {
|
||||
while (i < list.size()) {
|
||||
ElementDefinition defn = list.get(i);
|
||||
if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
|
||||
if (passElementPropsCheck(defn) && !Utilities.existsInList(defn.getName(), omitList)) {
|
||||
if (defn.getType().size() > 1) {
|
||||
// Debug/Utilities.breakpoint();
|
||||
i++;
|
||||
} else {
|
||||
String name = uniquefy(names, defn.getName());
|
||||
PEDefinitionElement pe = new PEDefinitionElement(this, name, profile, defn, parent.path());
|
||||
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
|
||||
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
|
||||
pe.setMustHaveValue(definition.getMustHaveValue());
|
||||
}
|
||||
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
|
||||
if (defn.hasSlicing()) {
|
||||
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
|
||||
res.add(pe);
|
||||
pe.setSlicer(true);
|
||||
}
|
||||
i++;
|
||||
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
|
||||
StructureDefinition ext = getExtensionDefinition(list.get(i));
|
||||
if (ext != null) {
|
||||
res.add(new PEDefinitionExtension(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, ext, parent.path()));
|
||||
} else if (isTypeSlicing(defn)) {
|
||||
res.add(new PEDefinitionTypeSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
} else {
|
||||
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
|
||||
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
|
||||
} else {
|
||||
res.add(new PEDefinitionSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
if (passElementPropsCheck(defn, inExtension) && !Utilities.existsInList(defn.getName(), omitList)) {
|
||||
String name = uniquefy(names, defn.getName());
|
||||
PEDefinitionElement pe = new PEDefinitionElement(this, name, profile, defn, parent.path());
|
||||
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
|
||||
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
|
||||
pe.setMustHaveValue(definition.getMustHaveValue());
|
||||
}
|
||||
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
|
||||
if (defn.hasSlicing()) {
|
||||
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
|
||||
res.add(pe);
|
||||
pe.setSlicer(true);
|
||||
}
|
||||
i++;
|
||||
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
|
||||
StructureDefinition ext = getExtensionDefinition(list.get(i));
|
||||
if (ext != null) {
|
||||
res.add(new PEDefinitionExtension(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, ext, parent.path()));
|
||||
} else if (isTypeSlicing(defn)) {
|
||||
res.add(new PEDefinitionTypeSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
} else {
|
||||
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
|
||||
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
|
||||
} else {
|
||||
res.add(new PEDefinitionSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
res.add(pe);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
@ -434,7 +430,10 @@ public class PEBuilder {
|
||||
return pe;
|
||||
}
|
||||
|
||||
private boolean passElementPropsCheck(ElementDefinition bdefn) {
|
||||
private boolean passElementPropsCheck(ElementDefinition bdefn, boolean inExtension) {
|
||||
if (inExtension) {
|
||||
return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id");
|
||||
}
|
||||
switch (elementProps) {
|
||||
case EXTENSION:
|
||||
return !Utilities.existsInList(bdefn.getBase().getPath(), "Element.id");
|
||||
|
@ -40,7 +40,9 @@ import org.hl7.fhir.r4.model.ElementDefinition;
|
||||
import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r4.model.StructureDefinition;
|
||||
import org.hl7.fhir.r4.model.Type;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.hl7.fhir.r4.profilemodel.PEDefinition.PEDefinitionElementMode;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public abstract class PEDefinition {
|
||||
@ -84,7 +86,7 @@ public abstract class PEDefinition {
|
||||
this.name = name;
|
||||
this.profile = profile;
|
||||
this.definition = definition;
|
||||
this.path = path == null ? name : ppath+"."+name;
|
||||
this.path = ppath == null ? name : ppath+"."+name;
|
||||
}
|
||||
|
||||
|
||||
@ -392,6 +394,34 @@ public abstract class PEDefinition {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getExtensionUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValueSet valueSet() {
|
||||
if (definition.getBinding().hasValueSet()) {
|
||||
return builder.getContext().fetchResource(ValueSet.class, definition.getBinding().getValueSet());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public PEBuilder getBuilder() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
public String typeSummary() {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (PEType t : types()) {
|
||||
b.append(t.getName());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
||||
public boolean isSlice() {
|
||||
return definition.hasSliceName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -114,5 +114,9 @@ public class PEDefinitionExtension extends PEDefinition {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getExtensionUrl() {
|
||||
return extension.getUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -113,5 +113,8 @@ public class PEDefinitionSubExtension extends PEDefinition {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getExtensionUrl() {
|
||||
return ued.getFixed().primitiveValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -906,6 +906,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
||||
return expandVS(vs, cacheOk, heirarchical, false, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, int count) {
|
||||
if (expParameters == null)
|
||||
throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
|
||||
Parameters p = expParameters.copy();
|
||||
p.addParameter("count", count);
|
||||
return expandVS(vs, cacheOk, heirarchical, false, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSetExpansionOutcome expandVS(String url, boolean cacheOk, boolean hierarchical, int count) {
|
||||
if (expParameters == null)
|
||||
|
@ -490,6 +490,8 @@ public interface IWorkerContext {
|
||||
*/
|
||||
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical);
|
||||
|
||||
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical, int count);
|
||||
|
||||
/**
|
||||
* ValueSet Expansion - see $expand
|
||||
*
|
||||
|
@ -30,15 +30,18 @@ import java.io.PrintStream;
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
|
||||
import org.hl7.fhir.r5.context.ContextUtilities;
|
||||
import org.hl7.fhir.r5.elementmodel.Element.SliceDefinition;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.extensions.ExtensionsUtils;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
@ -1227,6 +1230,12 @@ public class Element extends Base implements NamedItem {
|
||||
}
|
||||
}
|
||||
|
||||
public void removeChild(Element child) {
|
||||
if (children.removeIf(n -> n == child)) {
|
||||
children.clearMap();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isProhibited() {
|
||||
return prohibited;
|
||||
}
|
||||
|
@ -1,128 +0,0 @@
|
||||
package org.hl7.fhir.r5.liquid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.json.model.JsonArray;
|
||||
import org.hl7.fhir.utilities.json.model.JsonElement;
|
||||
|
||||
// this class exists to allow the Liquid Engine to be used against raw JSON
|
||||
|
||||
public class BaseCSVWrapper extends Base {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private List<String> columns;
|
||||
private List<String> values;
|
||||
private List<List<String>> rows;
|
||||
private String value;
|
||||
|
||||
private BaseCSVWrapper() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static BaseCSVWrapper forRows(List<String> columns, List<List<String>> rows) {
|
||||
BaseCSVWrapper self = new BaseCSVWrapper();
|
||||
self.columns = columns;
|
||||
self.rows = rows;
|
||||
return self;
|
||||
}
|
||||
|
||||
public static BaseCSVWrapper forRow(List<String> columns, List<String> values) {
|
||||
BaseCSVWrapper self = new BaseCSVWrapper();
|
||||
self.columns = columns;
|
||||
self.values = values;
|
||||
return self;
|
||||
}
|
||||
|
||||
public static BaseCSVWrapper forCell(String value) {
|
||||
BaseCSVWrapper self = new BaseCSVWrapper();
|
||||
self.value = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
if (values != null || rows != null) {
|
||||
return "Object";
|
||||
} else if (Utilities.existsInList(value, "true", "false")) {
|
||||
return "boolean";
|
||||
} else if (Utilities.isInteger(value)) {
|
||||
return "integer";
|
||||
} else if (Utilities.isDecimal(value, true)) {
|
||||
return "decimal";
|
||||
} else if (Utilities.isAbsoluteUrl(value)) {
|
||||
return "url";
|
||||
} else {
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdBase() {
|
||||
if (columns == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int i = columns.indexOf("id");
|
||||
if (i > -1) {
|
||||
return values.get(i);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdBase(String value) {
|
||||
throw new Error("BaseCSVWrapper is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base copy() {
|
||||
throw new Error("BaseCSVWrapper is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirPublication getFHIRPublicationVersion() {
|
||||
return FhirPublication.R5;
|
||||
}
|
||||
|
||||
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
|
||||
if (rows != null && "rows".equals(name)) {
|
||||
Base[] l = new Base[rows.size()];
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
l[i] = BaseCSVWrapper.forRow(columns, rows.get(i));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
if (values != null) {
|
||||
int i = columns.indexOf(name);
|
||||
if (i > -1) {
|
||||
Base[] l = new Base[1];
|
||||
l[0] = BaseCSVWrapper.forCell(values.get(i));
|
||||
return l;
|
||||
}
|
||||
}
|
||||
return super.getProperty(hash, name, checkValid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPrimitive() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String primitiveValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
package org.hl7.fhir.r5.liquid;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext.FunctionDefinition;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.testfactory.TestDataFactory.DataTable;
|
||||
import org.hl7.fhir.r5.testfactory.dataprovider.TableDataProvider;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
// this class exists to allow the Liquid Engine to be used against raw JSON
|
||||
|
||||
public class BaseTableWrapper extends Base {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private List<String> columns;
|
||||
private List<String> values;
|
||||
private List<List<String>> rows;
|
||||
private String value;
|
||||
private Map<String, DataTable> tables;
|
||||
|
||||
private BaseTableWrapper() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static BaseTableWrapper forRows(List<String> columns, List<List<String>> rows) {
|
||||
BaseTableWrapper self = new BaseTableWrapper();
|
||||
self.columns = columns;
|
||||
self.rows = rows;
|
||||
return self;
|
||||
}
|
||||
|
||||
public static BaseTableWrapper forRow(List<String> columns, List<String> values) {
|
||||
BaseTableWrapper self = new BaseTableWrapper();
|
||||
self.columns = columns;
|
||||
self.values = values;
|
||||
return self;
|
||||
}
|
||||
|
||||
public static BaseTableWrapper forCell(String value) {
|
||||
BaseTableWrapper self = new BaseTableWrapper();
|
||||
self.value = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
public static BaseTableWrapper forTable(TableDataProvider dt) {
|
||||
BaseTableWrapper self = new BaseTableWrapper();
|
||||
self.columns = dt.columns();
|
||||
self.rows = new ArrayList<>();
|
||||
dt.reset();
|
||||
while (dt.nextRow()) {
|
||||
self.rows.add(dt.cells());
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
public static BaseTableWrapper forTable(DataTable dt) {
|
||||
BaseTableWrapper self = new BaseTableWrapper();
|
||||
self.columns = dt.getColumns();
|
||||
self.rows = dt.getRows();
|
||||
return self;
|
||||
}
|
||||
|
||||
public Map<String, DataTable> getTables() {
|
||||
return tables;
|
||||
}
|
||||
|
||||
public BaseTableWrapper setTables(Map<String, DataTable> tables) {
|
||||
this.tables = tables;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
if (values != null || rows != null) {
|
||||
return "Object";
|
||||
} else if (Utilities.existsInList(value, "true", "false")) {
|
||||
return "boolean";
|
||||
} else if (Utilities.isInteger(value)) {
|
||||
return "integer";
|
||||
} else if (Utilities.isDecimal(value, true)) {
|
||||
return "decimal";
|
||||
} else if (Utilities.isAbsoluteUrl(value)) {
|
||||
return "url";
|
||||
} else {
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdBase() {
|
||||
if (columns == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int i = columns.indexOf("id");
|
||||
if (i > -1) {
|
||||
return values.get(i);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdBase(String value) {
|
||||
throw new Error("BaseTableWrapper is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base copy() {
|
||||
throw new Error("BaseTableWrapper is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirPublication getFHIRPublicationVersion() {
|
||||
return FhirPublication.R5;
|
||||
}
|
||||
|
||||
public String cell(String name) {
|
||||
if (values != null) {
|
||||
int i = columns.indexOf(name);
|
||||
if (i > -1) {
|
||||
return values.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
|
||||
if (rows != null && "rows".equals(name)) {
|
||||
Base[] l = new Base[rows.size()];
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
l[i] = BaseTableWrapper.forRow(columns, rows.get(i));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
if (values != null) {
|
||||
int i = columns.indexOf(name);
|
||||
if (i > -1) {
|
||||
Base[] l = new Base[1];
|
||||
l[0] = BaseTableWrapper.forCell(values.get(i));
|
||||
return l;
|
||||
}
|
||||
}
|
||||
if ("row".equals(name) && values != null) {
|
||||
Base[] l = new Base[1];
|
||||
l[0] = this;
|
||||
return l;
|
||||
}
|
||||
if (tables != null && tables.containsKey(name)) {
|
||||
Base[] l = new Base[1];
|
||||
l[0] = BaseTableWrapper.forTable(tables.get(name)).setTables(tables);
|
||||
return l;
|
||||
}
|
||||
return super.getProperty(hash, name, checkValid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isPrimitive() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String primitiveValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static class TableColumnFunction extends FunctionDefinition {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "column";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails details() {
|
||||
return new FunctionDetails("Look up a column by name", 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters) {
|
||||
if (focus.hasType("Table")) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, "string");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters) {
|
||||
List<Base> list = new ArrayList<>();
|
||||
if (focus.size() == 1 && focus.get(0) instanceof BaseTableWrapper && parameters.size() == 1 && parameters.get(0).size() == 1) {
|
||||
BaseTableWrapper tbl = (BaseTableWrapper) focus.get(0);
|
||||
String name = parameters.get(0).get(0).primitiveValue();
|
||||
if (tbl.columns.contains(name)) {
|
||||
list.add(new StringType(tbl.cell(name)));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TableDateColumnFunction extends FunctionDefinition {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "dateColumn";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails details() {
|
||||
return new FunctionDetails("read a date(/time) column with the specified format", 2, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters) {
|
||||
if (focus.hasType("Table")) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, "string");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters) {
|
||||
List<Base> list = new ArrayList<>();
|
||||
if (focus.size() == 1 && focus.get(0) instanceof BaseTableWrapper && parameters.size() == 2 && parameters.get(0).size() == 1 && parameters.get(1).size() == 1) {
|
||||
BaseTableWrapper tbl = (BaseTableWrapper) focus.get(0);
|
||||
String name = parameters.get(0).get(0).primitiveValue();
|
||||
String format = parameters.get(1).get(0).primitiveValue();
|
||||
if (tbl.columns.contains(name)) {
|
||||
String cell = tbl.cell(name);
|
||||
if (!Utilities.noString(cell)) {
|
||||
if ("excel.date".equals(format)) {
|
||||
list.add(new StringType(cell.substring(0, 10)));
|
||||
} else {
|
||||
try {
|
||||
list.add(new StringType(convertToFhirDateTime(cell, format)));
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException("Invalid date-time format: " + cell, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static String convertToFhirDateTime(String dateTime, String inputFormat) {
|
||||
DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputFormat, Locale.ENGLISH);
|
||||
DateTimeFormatter outputFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; // For FHIR DateTime with time zone
|
||||
|
||||
if (inputFormat.contains("Z") || inputFormat.contains("x") || inputFormat.contains("X")) {
|
||||
ZonedDateTime parsedDateTime = ZonedDateTime.parse(dateTime, inputFormatter);
|
||||
return parsedDateTime.format(outputFormatter);
|
||||
} else if (inputFormat.contains("h")) {
|
||||
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTime, inputFormatter);
|
||||
return parsedDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); // FHIR DateTime without time zone
|
||||
} else {
|
||||
LocalDate parsedDate = LocalDate.parse(dateTime, inputFormatter);
|
||||
return parsedDate.format(DateTimeFormatter.ISO_LOCAL_DATE); // FHIR DateTime without time zone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1020,4 +1020,10 @@ public class LiquidEngine implements IEvaluationContext {
|
||||
public boolean paramIsType(String name, int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public FHIRPathEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -32,25 +32,21 @@ package org.hl7.fhir.r5.model;
|
||||
// Generated on Thu, Mar 23, 2023 19:59+1100 for FHIR v5.0.0
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.r5.model.Enumerations.*;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.ChildOrder;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
import ca.uhn.fhir.model.api.annotation.Block;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
|
||||
import org.hl7.fhir.instance.model.api.ICompositeType;
|
||||
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
|
||||
import org.hl7.fhir.r5.model.Enumerations.BindingStrengthEnumFactory;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import ca.uhn.fhir.model.api.annotation.Block;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
/**
|
||||
* ElementDefinition Type: Captures constraints on each element within the resource, profile, or extension.
|
||||
*/
|
||||
|
@ -361,42 +361,37 @@ public class PEBuilder {
|
||||
ElementDefinition defn = list.get(i);
|
||||
if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
|
||||
if (passElementPropsCheck(defn, inExtension) && !Utilities.existsInList(defn.getName(), omitList)) {
|
||||
if (defn.getType().size() > 1) {
|
||||
// Debug/Utilities.breakpoint();
|
||||
i++;
|
||||
} else {
|
||||
String name = uniquefy(names, defn.getName());
|
||||
PEDefinitionElement pe = new PEDefinitionElement(this, name, profile, defn, parent.path());
|
||||
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
|
||||
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
|
||||
pe.setMustHaveValue(definition.getMustHaveValue());
|
||||
}
|
||||
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
|
||||
if (defn.hasSlicing()) {
|
||||
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
|
||||
res.add(pe);
|
||||
pe.setSlicer(true);
|
||||
}
|
||||
i++;
|
||||
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
|
||||
StructureDefinition ext = getExtensionDefinition(list.get(i));
|
||||
if (ext != null) {
|
||||
res.add(new PEDefinitionExtension(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, ext, parent.path()));
|
||||
} else if (isTypeSlicing(defn)) {
|
||||
res.add(new PEDefinitionTypeSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
} else {
|
||||
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
|
||||
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
|
||||
} else {
|
||||
res.add(new PEDefinitionSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
String name = uniquefy(names, defn.getName());
|
||||
PEDefinitionElement pe = new PEDefinitionElement(this, name, profile, defn, parent.path());
|
||||
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
|
||||
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
|
||||
pe.setMustHaveValue(definition.getMustHaveValue());
|
||||
}
|
||||
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
|
||||
if (defn.hasSlicing()) {
|
||||
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
|
||||
res.add(pe);
|
||||
pe.setSlicer(true);
|
||||
}
|
||||
i++;
|
||||
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
|
||||
StructureDefinition ext = getExtensionDefinition(list.get(i));
|
||||
if (ext != null) {
|
||||
res.add(new PEDefinitionExtension(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, ext, parent.path()));
|
||||
} else if (isTypeSlicing(defn)) {
|
||||
res.add(new PEDefinitionTypeSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
} else {
|
||||
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
|
||||
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
|
||||
} else {
|
||||
res.add(new PEDefinitionSlice(this, uniquefy(names, list.get(i).getSliceName()), profile, list.get(i), defn, parent.path()));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
res.add(pe);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
|
@ -33,16 +33,13 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.xmlbeans.impl.xb.xsdschema.All;
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.r5.context.ContextUtilities;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.DataType;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.profilemodel.PEDefinition.PEDefinitionElementMode;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public abstract class PEDefinition {
|
||||
@ -394,6 +391,9 @@ public abstract class PEDefinition {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getExtensionUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValueSet valueSet() {
|
||||
if (definition.getBinding().hasValueSet()) {
|
||||
@ -407,6 +407,19 @@ public abstract class PEDefinition {
|
||||
return builder;
|
||||
}
|
||||
|
||||
public String typeSummary() {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (PEType t : types()) {
|
||||
b.append(t.getName());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
||||
public boolean isSlice() {
|
||||
return definition.hasSliceName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -114,5 +114,9 @@ public class PEDefinitionExtension extends PEDefinition {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getExtensionUrl() {
|
||||
return extension.getUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -113,5 +113,8 @@ public class PEDefinitionSubExtension extends PEDefinition {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getExtensionUrl() {
|
||||
return ued.getFixed().primitiveValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,195 +0,0 @@
|
||||
package org.hl7.fhir.r5.profilemodel;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.CodeableConcept;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.DateType;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.Property;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
|
||||
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.utilities.Base64;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public class TestInstanceGenerator {
|
||||
|
||||
private IWorkerContext context;
|
||||
private Map<String, String> data;
|
||||
|
||||
public TestInstanceGenerator(IWorkerContext context) {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public byte[] generate(StructureDefinition profile, FhirFormat format) throws FHIRException, IOException {
|
||||
PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.NONE, true);
|
||||
PEDefinition definition = builder.buildPEDefinition(profile);
|
||||
Element element = Manager.build(context, profile);
|
||||
|
||||
populateByProfile(element, definition, 0);
|
||||
|
||||
ByteArrayOutputStream ba = new ByteArrayOutputStream();
|
||||
Manager.compose(context, element, ba, format, OutputStyle.PRETTY, null);
|
||||
return ba.toByteArray();
|
||||
}
|
||||
|
||||
protected void populateByProfile(Element element, PEDefinition definition, int level) {
|
||||
if (definition.types().size() == 1) {
|
||||
for (PEDefinition pe : definition.directChildren(true)) {
|
||||
if (pe.max() > 0 && (!isIgnoredElement(pe.definition().getBase().getPath()) || pe.hasFixedValue())) {
|
||||
populateElement(element, pe, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIgnoredElement(String path) {
|
||||
return Utilities.existsInList(path, "Identifier.assigner", "Resource.meta", "DomainResource.text", "Resource.implicitRules");
|
||||
}
|
||||
|
||||
public void populateElement(Element element, PEDefinition pe, int level) {
|
||||
System.out.println(pe.path());
|
||||
if (!pe.isSlicer() && isNonAbstractType(pe)) {
|
||||
if (pe.hasFixedValue()) {
|
||||
Element focus = element.addElement(pe.schemaName());
|
||||
Base fv = pe.definition().hasPattern() ? pe.definition().getPattern() : pe.definition().getFixed();
|
||||
if (fv.isPrimitive()) {
|
||||
focus.setValue(fv.primitiveValue());
|
||||
} else {
|
||||
populateElementFromDataType(element, fv, null);
|
||||
}
|
||||
} else {
|
||||
makeChildElement(element, pe, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNonAbstractType(PEDefinition pe) {
|
||||
for (PEType t : pe.types()) {
|
||||
if (!pe.getBuilder().getContextUtilities().isAbstractType(t.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void makeChildElement(Element element, PEDefinition pe, int level) {
|
||||
Element b = null;
|
||||
if (pe.schemaName().endsWith("[x]")) {
|
||||
if (pe.types().size() == 1) {
|
||||
b = element.makeElement(pe.schemaName().replace("[x]", Utilities.capitalize(pe.types().get(0).getType())));
|
||||
}
|
||||
} else {
|
||||
b = element.makeElement(pe.schemaName());
|
||||
}
|
||||
if (b != null) {
|
||||
if (pe.definition.hasBinding()) {
|
||||
ValueSet vs = pe.valueSet();
|
||||
if (vs != null) {
|
||||
ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
|
||||
if (vse.isOk()) {
|
||||
ValueSetExpansionContainsComponent cc = pickRandomConcept(vse.getValueset().getExpansion().getContains());
|
||||
if (b.isPrimitive()) {
|
||||
b.setValue(vse.getValueset().getExpansion().getContainsFirstRep().getCode());
|
||||
} else if ("Coding".equals(b.fhirType())) {
|
||||
populateElementFromDataType(b, new Coding(cc), null);
|
||||
} else if ("CodeableConcept".equals(b.fhirType())) {
|
||||
populateElementFromDataType(b, new CodeableConcept(new Coding(cc)), null);
|
||||
}
|
||||
} else {
|
||||
System.out.println(" ValueSet Error: "+vse.getError());
|
||||
}
|
||||
} else {
|
||||
System.out.println(" Unknown ValueSet: "+pe.definition.getBinding().getValueSet());
|
||||
}
|
||||
} else if (b.isPrimitive()) {
|
||||
switch (b.fhirType()) {
|
||||
case "id":
|
||||
b.setValue(UUID.randomUUID().toString().toLowerCase());
|
||||
break;
|
||||
case "string":
|
||||
b.setValue("Some String value");
|
||||
break;
|
||||
case "base64Binary" :
|
||||
b.setValue(java.util.Base64.getMimeEncoder().encodeToString("Some Binary Value".getBytes(StandardCharsets.UTF_8)));
|
||||
break;
|
||||
case "boolean" :
|
||||
b.setValue(ThreadLocalRandom.current().nextInt(0, 2) == 1 ? "true" : "false");
|
||||
break;
|
||||
case "date" :
|
||||
b.setValue(new DateType(new Date()).asStringValue());
|
||||
break;
|
||||
case "dateTime":
|
||||
b.setValue(new DateTimeType(new Date()).asStringValue());
|
||||
break;
|
||||
case "positiveInt" :
|
||||
b.setValue(Integer.toString(ThreadLocalRandom.current().nextInt(1, 1000)));
|
||||
break;
|
||||
case "usignedInt" :
|
||||
b.setValue(Integer.toString(ThreadLocalRandom.current().nextInt(0, 1000)));
|
||||
break;
|
||||
case "url" :
|
||||
b.setValue("http://some.url/path");
|
||||
break;
|
||||
|
||||
default:
|
||||
System.out.println("Unhandled type: "+b.fhirType());
|
||||
}
|
||||
} else {
|
||||
populateByProfile(b, pe, level+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValueSetExpansionContainsComponent pickRandomConcept(List<ValueSetExpansionContainsComponent> list) {
|
||||
ValueSetExpansionContainsComponent res = null;
|
||||
while (res == null) {
|
||||
int r = ThreadLocalRandom.current().nextInt(0, list.size());
|
||||
if (list.get(r).getAbstract()) {
|
||||
if (list.get(r).hasContains()) {
|
||||
res = pickRandomConcept(list.get(0).getContains());
|
||||
}
|
||||
} else {
|
||||
res = list.get(r);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private void populateElementFromDataType(Element element, Base source, PEDefinition defn) {
|
||||
for (Property prop : source.children()) {
|
||||
for (Base b : prop.getValues()) {
|
||||
Element child = element.makeElement(prop.getName());
|
||||
if (b.isPrimitive()) {
|
||||
child.setValue(b.primitiveValue());
|
||||
} else {
|
||||
populateElementFromDataType(child, b, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,618 @@
|
||||
package org.hl7.fhir.r5.testfactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r5.liquid.BaseTableWrapper;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.DataType;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.DateType;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r5.model.Property;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.profilemodel.PEBuilder;
|
||||
import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;
|
||||
import org.hl7.fhir.r5.profilemodel.PEDefinition;
|
||||
import org.hl7.fhir.r5.profilemodel.PEType;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
|
||||
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.testfactory.TestDataFactory.DataTable;
|
||||
import org.hl7.fhir.r5.testfactory.dataprovider.BaseDataTableProvider;
|
||||
import org.hl7.fhir.r5.testfactory.dataprovider.TableDataProvider;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.json.JsonException;
|
||||
import org.hl7.fhir.utilities.json.model.JsonArray;
|
||||
import org.hl7.fhir.utilities.json.model.JsonElement;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* see https://build.fhir.org/ig/FHIR/ig-guidance/testfactory.html for doco
|
||||
*
|
||||
*/
|
||||
public class ProfileBasedFactory {
|
||||
|
||||
private BaseDataTableProvider baseData;
|
||||
private TableDataProvider data;
|
||||
private JsonArray mappings;
|
||||
private Map<String, DataTable> tables;
|
||||
private FHIRPathEngine fpe;
|
||||
private PrintStream log;
|
||||
private boolean testing;
|
||||
|
||||
private static class LogSet {
|
||||
public LogSet(String msg) {
|
||||
line.append(msg);
|
||||
}
|
||||
private StringBuilder line = new StringBuilder();
|
||||
private List<String> others = new ArrayList<>();
|
||||
}
|
||||
private List<LogSet> logEntries = new ArrayList<>();
|
||||
|
||||
public ProfileBasedFactory(FHIRPathEngine fpe, String baseDataSource) throws JsonException, IOException, SQLException {
|
||||
super();
|
||||
this.fpe = fpe;
|
||||
baseData = new BaseDataTableProvider(baseDataSource);
|
||||
}
|
||||
|
||||
public ProfileBasedFactory(FHIRPathEngine fpe, String baseDataSource, TableDataProvider data, Map<String, DataTable> tables, JsonArray mappings) throws JsonException, IOException, SQLException {
|
||||
super();
|
||||
this.fpe = fpe;
|
||||
baseData = new BaseDataTableProvider(baseDataSource);
|
||||
this.data = data;
|
||||
this.tables = tables;
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
public byte[] generateFormat(StructureDefinition profile, FhirFormat format) throws FHIRException, IOException, SQLException {
|
||||
PEBuilder builder = new PEBuilder(fpe.getWorker(), PEElementPropertiesPolicy.NONE, true);
|
||||
PEDefinition definition = builder.buildPEDefinition(profile);
|
||||
Element element = Manager.build(fpe.getWorker(), profile);
|
||||
|
||||
log("--------------------------------");
|
||||
log("Build Row "+data.cell("counter")+" for "+profile.getVersionedUrl());
|
||||
if (data != null) {
|
||||
log("Row Data: "+CommaSeparatedStringBuilder.join(",", data.cells()));
|
||||
}
|
||||
populateByProfile(element, definition, 0, null, null);
|
||||
for (LogSet ls : logEntries) {
|
||||
log(ls.line.toString());
|
||||
for (String s : ls.others) {
|
||||
log(" "+s);
|
||||
}
|
||||
}
|
||||
log("--------------------------------");
|
||||
logEntries.clear();
|
||||
|
||||
ByteArrayOutputStream ba = new ByteArrayOutputStream();
|
||||
Manager.compose(fpe.getWorker(), element, ba, format, OutputStyle.PRETTY, null);
|
||||
return ba.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public Element generate(StructureDefinition profile) throws FHIRException, IOException, SQLException {
|
||||
PEBuilder builder = new PEBuilder(fpe.getWorker(), PEElementPropertiesPolicy.NONE, true);
|
||||
PEDefinition definition = builder.buildPEDefinition(profile);
|
||||
Element element = Manager.build(fpe.getWorker(), profile);
|
||||
|
||||
log("--------------------------------");
|
||||
log("Build Row "+data.cell("counter")+" for "+profile.getVersionedUrl());
|
||||
if (data != null) {
|
||||
log("Row Data: "+CommaSeparatedStringBuilder.join(",", data.cells()));
|
||||
}
|
||||
populateByProfile(element, definition, 0, null, null);
|
||||
for (LogSet ls : logEntries) {
|
||||
log(ls.line.toString());
|
||||
for (String s : ls.others) {
|
||||
log(" "+s);
|
||||
}
|
||||
}
|
||||
log("--------------------------------");
|
||||
logEntries.clear();
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
protected void populateByProfile(Element element, PEDefinition definition, int level, String path, Map<String, String> values) throws SQLException, IOException {
|
||||
if (definition.types().size() == 1) {
|
||||
for (PEDefinition pe : definition.directChildren(true)) {
|
||||
if (pe.max() > 0 && (!isIgnoredElement(pe.definition().getBase().getPath()) || pe.hasFixedValue())) {
|
||||
populateElement(element, pe, level, path, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIgnoredElement(String path) {
|
||||
return Utilities.existsInList(path, "Identifier.assigner", "Resource.meta", "DomainResource.text", "Resource.implicitRules");
|
||||
}
|
||||
|
||||
public void populateElement(Element element, PEDefinition pe, int level, String path, Map<String, String> values) throws SQLException, IOException {
|
||||
LogSet ls = new LogSet(pe.path()+" : ");
|
||||
logEntries.add(ls);
|
||||
if (!pe.isExtension() && "Extension".equals(pe.typeSummary())) {
|
||||
ls.line.append("ignore unprofiled extension");
|
||||
} else if (pe.isSlicer()) {
|
||||
ls.line.append("ignore (slicer)");
|
||||
} else if (isNonAbstractType(pe) || pe.hasFixedValue() || pe.definition().getBase().getPath().equals("Resource.id")) {
|
||||
if (pe.hasFixedValue()) {
|
||||
Element focus = element.addElement(pe.schemaName());
|
||||
Base fv = pe.definition().hasPattern() ? pe.definition().getPattern() : pe.definition().getFixed();
|
||||
if (fv.isPrimitive()) {
|
||||
ls.line.append("fixed value = "+fv.primitiveValue());
|
||||
focus.setValue(fv.primitiveValue());
|
||||
} else {
|
||||
ls.line.append("fixed value = "+new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.NORMAL).composeString((DataType) fv, "data"));
|
||||
populateElementFromDataType(focus, fv, null);
|
||||
}
|
||||
} else {
|
||||
if (pe.isSlice() && values != null) {
|
||||
values = null;
|
||||
ls.others.add("slice, so ignore values from parent");
|
||||
}
|
||||
makeChildElement(element, pe, level, path, values, ls);
|
||||
}
|
||||
} else {
|
||||
ls.line.append("ignore (type = "+pe.typeSummary()+")");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNonAbstractType(PEDefinition pe) {
|
||||
for (PEType t : pe.types()) {
|
||||
if (!pe.getBuilder().getContextUtilities().isAbstractType(t.getType()) || Utilities.existsInList(t.getType(), "BackboneElement", "BackboneType")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void makeChildElement(Element element, PEDefinition pe, int level, String path, Map<String, String> values, LogSet ls) throws SQLException, IOException {
|
||||
Element b = null;
|
||||
if (pe.schemaName().endsWith("[x]")) {
|
||||
if (pe.types().size() == 1) {
|
||||
b = element.makeElement(pe.schemaName().replace("[x]", Utilities.capitalize(pe.types().get(0).getType())));
|
||||
} else {
|
||||
// we could pick any, but which we pick might be dictated by the value provider
|
||||
String t = getValueType(ls, path, pe.path(), pe.definition().getId(), pe.definition().getPath());
|
||||
if (t == null) {
|
||||
// all right we just pick one
|
||||
t = pe.types().get(testing ? 0 : ThreadLocalRandom.current().nextInt(0, pe.types().size())).getType();
|
||||
}
|
||||
if (t == null) {
|
||||
ls.line.append("ignored because polymorphic and no type");
|
||||
} else {
|
||||
b = element.makeElement(pe.schemaName().replace("[x]", Utilities.capitalize(t)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b = element.makeElement(pe.schemaName());
|
||||
}
|
||||
if (b != null) {
|
||||
if (b.isPrimitive()) {
|
||||
String val = null;
|
||||
if (values != null) {
|
||||
val = values.get(b.getName());
|
||||
if (pe.path().endsWith(".display")) {
|
||||
if (!valuesMatch(values.get("system"), b.getNamedChildValue("system")) || !valuesMatch(values.get("code"), b.getNamedChildValue("code"))) {
|
||||
val = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (values == null || val != null || pe.min() > 0) {
|
||||
if (val == null && data != null) {
|
||||
val = getPrimitiveValue(ls, b.fhirType(), path, pe.path(), pe.definition().getId(), pe.definition().getPath());
|
||||
}
|
||||
if (val == null && pe.valueSet() != null) {
|
||||
ValueSetExpansionContainsComponent cc = doExpansion(ls, pe.valueSet());
|
||||
if (cc != null) {
|
||||
val = cc.getCode();
|
||||
}
|
||||
}
|
||||
if (val == null) {
|
||||
val = getBasePrimitiveValue(ls, pe, path, b);
|
||||
}
|
||||
if (val != null) {
|
||||
if (Utilities.noString(val)) {
|
||||
ls.line.append(" value suppressed");
|
||||
element.removeChild(b);
|
||||
} else {
|
||||
ls.line.append("from value "+val);
|
||||
b.setValue(val);
|
||||
}
|
||||
} else {
|
||||
ls.line.append(" fake value");
|
||||
switch (b.fhirType()) {
|
||||
case "id":
|
||||
b.setValue(makeUUID());
|
||||
break;
|
||||
case "string":
|
||||
b.setValue("Some String value");
|
||||
break;
|
||||
case "base64Binary" :
|
||||
b.setValue(java.util.Base64.getMimeEncoder().encodeToString("Some Binary Value".getBytes(StandardCharsets.UTF_8)));
|
||||
break;
|
||||
case "boolean" :
|
||||
b.setValue(testing ? "true" : ThreadLocalRandom.current().nextInt(0, 2) == 1 ? "true" : "false");
|
||||
break;
|
||||
case "date" :
|
||||
b.setValue(new DateType(new Date()).asStringValue());
|
||||
break;
|
||||
case "dateTime":
|
||||
b.setValue(new DateTimeType(new Date()).asStringValue());
|
||||
break;
|
||||
case "positiveInt" :
|
||||
b.setValue(Integer.toString(testing ? 1 : ThreadLocalRandom.current().nextInt(1, 1000)));
|
||||
break;
|
||||
case "usignedInt" :
|
||||
b.setValue(Integer.toString(testing ? 2 : ThreadLocalRandom.current().nextInt(0, 1000)));
|
||||
break;
|
||||
case "url" :
|
||||
b.setValue("http://some.url/path");
|
||||
break;
|
||||
|
||||
default:
|
||||
ls.others.add("Unhandled type: "+b.fhirType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ls.line.append(" omitted - not in values");
|
||||
}
|
||||
} else {
|
||||
boolean build = true;
|
||||
if (values != null) {
|
||||
values = filterValues(values, b.getName());
|
||||
if (values == null && pe.min() == 0) {
|
||||
build = false;
|
||||
}
|
||||
}
|
||||
if (build) {
|
||||
if (pe.isExtension()) {
|
||||
if (Utilities.isAbsoluteUrl(pe.getExtensionUrl()) || path == null) {
|
||||
path = pe.getExtensionUrl();
|
||||
} else {
|
||||
path = path+"."+pe.getExtensionUrl();
|
||||
}
|
||||
}
|
||||
if (values == null && data != null) {
|
||||
values = getComplexValue(ls, b.fhirType(), path, pe.path(), pe.definition().getId(), pe.definition().getPath());
|
||||
}
|
||||
if (values == null && pe.valueSet() != null) {
|
||||
ValueSetExpansionContainsComponent cc = doExpansion(ls, pe.valueSet());
|
||||
if (cc != null) {
|
||||
values = makeValuesForCodedValue(ls, b.fhirType(), cc);
|
||||
}
|
||||
}
|
||||
if (values == null) {
|
||||
if ("Reference".equals(b.fhirType()) && values == null) {
|
||||
List<CanonicalType> targets = new ArrayList<>();
|
||||
for (TypeRefComponent tr : pe.definition().getType()) {
|
||||
if (tr.getWorkingCode().equals("Reference")) {
|
||||
targets.addAll(tr.getTargetProfile());
|
||||
}
|
||||
}
|
||||
List<String> choices = new ArrayList<>();
|
||||
for (CanonicalType ct : targets) {
|
||||
StructureDefinition sd = fpe.getWorker().fetchResource(StructureDefinition.class, ct.primitiveValue());
|
||||
if (!Utilities.existsInList(sd.getType(), "Resource", "DomainResource")) {
|
||||
choices.add(sd.getType());
|
||||
}
|
||||
}
|
||||
if (choices.isEmpty()) {
|
||||
choices.addAll(fpe.getWorker().getResourceNames());
|
||||
}
|
||||
String resType = choices.get(testing ? 0 : ThreadLocalRandom.current().nextInt(0, choices.size()));
|
||||
values = new HashMap<String, String>();
|
||||
values.put("reference", resType+"/"+makeUUID());
|
||||
ls.others.add("construct reference to "+resType+" from choices: "+CommaSeparatedStringBuilder.join("|", choices));
|
||||
} else {
|
||||
values = getBaseComplexValue(ls, pe, path, b);
|
||||
}
|
||||
}
|
||||
if (values == null) {
|
||||
ls.line.append(" populate children");
|
||||
} else if (values.isEmpty()) {
|
||||
ls.line.append(" don't populate - no children");
|
||||
} else {
|
||||
ls.line.append(" populate children from "+values.toString());
|
||||
}
|
||||
if (values == null || !values.isEmpty()) {
|
||||
populateByProfile(b, pe, level+1, path, values);
|
||||
if (!b.hasChildren() && !b.hasValue()) {
|
||||
element.removeChild(b);
|
||||
}
|
||||
} else {
|
||||
element.removeChild(b);
|
||||
}
|
||||
} else {
|
||||
ls.line.append(" omitted - values have no value");
|
||||
element.removeChild(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String makeUUID() {
|
||||
return testing ? "6e4d3a43-6642-4a0b-9b67-48c29af581a9" : UUID.randomUUID().toString().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
private boolean valuesMatch(String v1, String v2) {
|
||||
if (v1 == null) {
|
||||
return v2 == null;
|
||||
} else {
|
||||
return v1.equals(v2);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFixedChildren(PEDefinition definition) {
|
||||
if (definition.types().size() != 1) {
|
||||
return false;
|
||||
}
|
||||
for (PEDefinition pe : definition.directChildren(true)) {
|
||||
if (pe.hasFixedValue()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> makeValuesForCodedValue(LogSet ls, String fhirType, ValueSetExpansionContainsComponent cc) {
|
||||
Map<String, String> res = new HashMap<>();
|
||||
switch (fhirType) {
|
||||
case "Coding":
|
||||
res.put("system", cc.getSystem());
|
||||
if (cc.hasVersion()) {
|
||||
res.put("version", cc.getVersion());
|
||||
}
|
||||
res.put("code", cc.getCode());
|
||||
if (cc.hasDisplay()) {
|
||||
res.put("display", cc.getDisplay());
|
||||
}
|
||||
break;
|
||||
case "CodeableConcept":
|
||||
res.put("coding.system", cc.getSystem());
|
||||
if (cc.hasVersion()) {
|
||||
res.put("coding.version", cc.getVersion());
|
||||
}
|
||||
res.put("coding.code", cc.getCode());
|
||||
if (cc.hasDisplay()) {
|
||||
res.put("coding.display", cc.getDisplay());
|
||||
}
|
||||
break;
|
||||
case "CodedReference":
|
||||
res.put("concept.coding.system", cc.getSystem());
|
||||
if (cc.hasVersion()) {
|
||||
res.put("concept.coding.version", cc.getVersion());
|
||||
}
|
||||
res.put("concept.coding.code", cc.getCode());
|
||||
if (cc.hasDisplay()) {
|
||||
res.put("concept.coding.display", cc.getDisplay());
|
||||
}
|
||||
break;
|
||||
case "Quantity":
|
||||
res.put("system", cc.getSystem());
|
||||
res.put("code", cc.getCode());
|
||||
if (cc.hasDisplay()) {
|
||||
res.put("unit", cc.getDisplay());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ls.others.add("Unknown type handling coded value: "+fhirType);
|
||||
return null;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private ValueSetExpansionContainsComponent doExpansion(LogSet ls, ValueSet vs) {
|
||||
ValueSetExpansionOutcome vse = fpe.getWorker().expandVS(vs, true, false, 100);
|
||||
if (vse.isOk()) {
|
||||
ls.others.add("ValueSet "+vs.getVersionedUrl()+" "+ValueSetUtilities.countExpansion(vse.getValueset().getExpansion().getContains())+" concepts");
|
||||
return pickRandomConcept(vse.getValueset().getExpansion().getContains());
|
||||
} else {
|
||||
ls.others.add("ValueSet "+vs.getVersionedUrl()+": error = "+vse.getError());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getBaseComplexValue(LogSet ls, PEDefinition pe, String path, Element b) throws SQLException {
|
||||
Map<String, String> result = baseData.getComplexValue(path != null ? path : pe.definition().getId(), b.fhirType());
|
||||
if (result == null) {
|
||||
ls.others.add("No base data for "+path+":"+b.fhirType());
|
||||
} else {
|
||||
ls.others.add("Base data for "+path+":"+b.fhirType()+" = "+result.toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String getBasePrimitiveValue(LogSet ls, PEDefinition pe, String path, Element b) throws SQLException {
|
||||
String result = baseData.getPrimitiveValue(path != null ? path : pe.definition().getId(), b.fhirType());
|
||||
if (result == null) {
|
||||
ls.others.add("No base data for "+path+":"+b.fhirType());
|
||||
} else {
|
||||
ls.others.add("Base data for "+path+":"+b.fhirType()+" = "+result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> filterValues(Map<String, String> values, String name) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (String s : values.keySet()) {
|
||||
if (s.startsWith(name+".")) {
|
||||
result.put(s.substring(name.length()+1), values.get(s));
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private ValueSetExpansionContainsComponent pickRandomConcept(List<ValueSetExpansionContainsComponent> list) {
|
||||
ValueSetExpansionContainsComponent res = null;
|
||||
int i = 0;
|
||||
while (res == null && list.size() > 0) {
|
||||
int r = testing ? i : ThreadLocalRandom.current().nextInt(0, list.size());
|
||||
if (list.get(r).getAbstract()) {
|
||||
if (list.get(r).hasContains()) {
|
||||
res = pickRandomConcept(list.get(0).getContains());
|
||||
}
|
||||
} else {
|
||||
res = list.get(r);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private void populateElementFromDataType(Element element, Base source, PEDefinition defn) {
|
||||
for (Property prop : source.children()) {
|
||||
for (Base b : prop.getValues()) {
|
||||
Element child = element.makeElement(prop.getName());
|
||||
if (b.isPrimitive()) {
|
||||
child.setValue(b.primitiveValue());
|
||||
} else {
|
||||
populateElementFromDataType(child, b, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getValueType(LogSet ls, String... ids) {
|
||||
JsonObject entry = findMatchingEntry(ls, ids);
|
||||
if (entry != null) {
|
||||
JsonElement fhirType = entry.get("fhirType");
|
||||
if (fhirType == null || !fhirType.isJsonPrimitive() || Utilities.noString(fhirType.asString())) {
|
||||
return "";
|
||||
} else {
|
||||
String ft = fhirType.asString();
|
||||
StructureDefinition sd = fpe.getWorker().fetchTypeDefinition(ft);
|
||||
if (sd != null) {
|
||||
return ft;
|
||||
} else {
|
||||
return evaluateExpression(ls.others, fhirType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPrimitiveValue(LogSet ls, String fhirType, String... ids) {
|
||||
JsonObject entry = findMatchingEntry(ls, ids);
|
||||
if (entry != null) {
|
||||
JsonElement expression = entry.get("expression");
|
||||
if (expression == null || !expression.isJsonPrimitive() || Utilities.noString(expression.asString())) {
|
||||
ls.others.add("Found an entry for "+entry.asString("path")+" but it had no expression");
|
||||
return "";
|
||||
} else {
|
||||
return evaluateExpression(ls.others, expression, null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String evaluateExpression(List<String> log, JsonElement expression, String name) {
|
||||
ExpressionNode expr = (ExpressionNode) expression.getUserData("compiled");
|
||||
if (expr == null) {
|
||||
expr = fpe.parse(expression.asString());
|
||||
expression.setUserData("compiled", expr);
|
||||
}
|
||||
BaseTableWrapper csv = BaseTableWrapper.forRow(data.columns(), data.cells()).setTables(tables);
|
||||
|
||||
String val = null;
|
||||
try {
|
||||
val = fpe.evaluateToString(null, null, null, csv, expr);
|
||||
log.add(name+" ==> '"+val+"' (from "+expr.toString()+")");
|
||||
} catch (Exception e) {
|
||||
log.add(name+" ==> null because "+e.getMessage()+" (from "+expr.toString()+")");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private JsonObject findMatchingEntry(LogSet ls, String[] ids) {
|
||||
for (JsonObject entry : mappings.asJsonObjects()) {
|
||||
if (Utilities.existsInList(entry.asString("path"), ids)) {
|
||||
boolean use = true;
|
||||
if (entry.has("if")) {
|
||||
use = Utilities.existsInList(evaluateExpression(ls.others, entry.get("if"), "if"), "1", "true");
|
||||
}
|
||||
if (use) {
|
||||
ls.others.add("mapping entry for "+entry.asString("path")+" from ids "+CommaSeparatedStringBuilder.join(";", ids));
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
ls.others.add("mapping entry not found for ids "+CommaSeparatedStringBuilder.join(";", ids));
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, String> getComplexValue(LogSet ls, String fhirType, String... ids) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
JsonObject entry = findMatchingEntry(ls, ids);
|
||||
if (entry != null) {
|
||||
JsonArray a = entry.forceArray("parts");
|
||||
if (a.size() == 0) {
|
||||
return result;
|
||||
} else {
|
||||
for (JsonObject src : a.asJsonObjects()) {
|
||||
if (!src.has("name")) {
|
||||
throw new FHIRException("Found an entry for "+entry.asString("path")+" but it had no proeprty name");
|
||||
}
|
||||
result.put(src.asString("name"), evaluateExpression(ls.others, src.get("expression"), src.asString("name")));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.isEmpty() ? null : result;
|
||||
}
|
||||
|
||||
|
||||
private void log(String msg) throws IOException {
|
||||
if (log != null) {
|
||||
log.append(msg+"\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
public PrintStream getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
public void setLog(PrintStream log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public boolean isTesting() {
|
||||
return testing;
|
||||
}
|
||||
|
||||
public void setTesting(boolean testing) {
|
||||
this.testing = testing;
|
||||
baseData.setTesting(testing);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,506 @@
|
||||
package org.hl7.fhir.r5.testfactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext.FunctionDefinition;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r5.liquid.BaseTableWrapper;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine.LiquidDocument;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.testfactory.dataprovider.TableDataProvider;
|
||||
import org.hl7.fhir.r5.testfactory.dataprovider.ValueSetDataProvider;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.FhirPublication;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.http.HTTPResult;
|
||||
import org.hl7.fhir.utilities.http.ManagedWebAccess;
|
||||
import org.hl7.fhir.utilities.json.JsonException;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
|
||||
import ca.uhn.fhir.context.support.IValidationSupport.ValueSetExpansionOutcome;
|
||||
|
||||
public class TestDataFactory {
|
||||
|
||||
public static class DataTable extends Base {
|
||||
List<String> columns = new ArrayList<String>();
|
||||
List<List<String>> rows = new ArrayList<List<String>>();
|
||||
|
||||
@Override
|
||||
public String fhirType() {
|
||||
return "DataTable";
|
||||
}
|
||||
@Override
|
||||
public String getIdBase() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public void setIdBase(String value) {
|
||||
throw new Error("Readonly");
|
||||
}
|
||||
@Override
|
||||
public Base copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public List<String> getColumns() {
|
||||
return columns;
|
||||
}
|
||||
public List<List<String>> getRows() {
|
||||
return rows;
|
||||
}
|
||||
@Override
|
||||
public FhirPublication getFHIRPublicationVersion() {
|
||||
return FhirPublication.R5;
|
||||
}
|
||||
|
||||
public String cell(int row, String col) {
|
||||
if (row >= 0 && row < rows.size()) {
|
||||
List<String> r = rows.get(row);
|
||||
int c = -1;
|
||||
if (Utilities.isInteger(col)) {
|
||||
c = Utilities.parseInt(col, -1);
|
||||
} else {
|
||||
c = columns.indexOf(col);
|
||||
}
|
||||
if (c > -1 && c < r.size()) {
|
||||
return r.get(c);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public String lookup(String lcol, String val, String rcol) {
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
if (val.equals(cell(i, lcol))) {
|
||||
return cell(i, rcol);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CellLookupFunction extends FunctionDefinition {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "cell";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails details() {
|
||||
return new FunctionDetails("Lookup a data element", 2, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, "string");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters) {
|
||||
int row = Utilities.parseInt(parameters.get(0).get(0).primitiveValue(), 0);
|
||||
String col = parameters.get(1).get(0).primitiveValue();
|
||||
DataTable dt = (DataTable) focus.get(0);
|
||||
|
||||
List<Base> res = new ArrayList<Base>();
|
||||
String s = dt.cell(row, col);
|
||||
if (!Utilities.noString(s)) {
|
||||
res.add(new StringType(s));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TableLookupFunction extends FunctionDefinition {
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "lookup";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails details() {
|
||||
return new FunctionDetails("Lookup a value in a table", 4, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails check(FHIRPathEngine engine, Object appContext, TypeDetails focus, List<TypeDetails> parameters) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, "string");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> execute(FHIRPathEngine engine, Object appContext, List<Base> focus, List<List<Base>> parameters) {
|
||||
|
||||
List<Base> res = new ArrayList<Base>();
|
||||
if (focus.get(0) instanceof BaseTableWrapper && parameters.size() == 4 && parameters.get(0).size() == 1 && parameters.get(1).size() == 1 && parameters.get(2).size() == 1 && parameters.get(3).size() == 1) {
|
||||
BaseTableWrapper dt = (BaseTableWrapper) focus.get(0);
|
||||
String table = parameters.get(0).get(0).primitiveValue();
|
||||
String lcol = parameters.get(1).get(0).primitiveValue();
|
||||
String val = parameters.get(2).get(0).primitiveValue();
|
||||
String rcol = parameters.get(3).get(0).primitiveValue();
|
||||
if (table != null && lcol != null && val != null && rcol != null) {
|
||||
DataTable tbl = dt.getTables().get(table);
|
||||
if (tbl != null) {
|
||||
String s = tbl.lookup(lcol, val, rcol);
|
||||
if (!Utilities.noString(s)) {
|
||||
res.add(new StringType(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String rootFolder;
|
||||
private LiquidEngine liquid;
|
||||
private PrintStream log;
|
||||
private IWorkerContext context;
|
||||
private String canonical;
|
||||
private FhirFormat format;
|
||||
private File localData;
|
||||
private FHIRPathEngine fpe;
|
||||
private JsonObject details;
|
||||
private String name;
|
||||
private boolean testing;
|
||||
|
||||
public TestDataFactory(IWorkerContext context, JsonObject details, LiquidEngine liquid, FHIRPathEngine fpe, String canonical, String rootFolder, String logFolder) throws IOException {
|
||||
super();
|
||||
this.context = context;
|
||||
this.rootFolder = rootFolder;
|
||||
this.canonical = canonical;
|
||||
this.details = details;
|
||||
this.liquid = liquid;
|
||||
this.fpe = fpe;
|
||||
|
||||
this.name = details.asString("name");
|
||||
if (Utilities.noString(name)) {
|
||||
throw new FHIRException("Factory has no name");
|
||||
}
|
||||
log = new PrintStream(new FileOutputStream(Utilities.path(logFolder, name+".log")));
|
||||
format = "json".equals(details.asString("format")) ? FhirFormat.JSON : FhirFormat.XML;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void execute() throws FHIRException, IOException {
|
||||
String mode = details.asString( "mode");
|
||||
if ("liquid".equals(mode)) {
|
||||
executeLiquid();
|
||||
} else if ("profile".equals(mode)) {
|
||||
executeProfile();
|
||||
} else {
|
||||
error("Factory "+getName()+" mode '"+mode+"' unknown");
|
||||
}
|
||||
log("finished successfully");
|
||||
log.close();
|
||||
}
|
||||
|
||||
|
||||
private void logDataScheme(DataTable tbl, Map<String, DataTable> tables) throws IOException {
|
||||
log("data: "+CommaSeparatedStringBuilder.join(",", tbl.getColumns()));
|
||||
for (String tn : Utilities.sorted(tables.keySet())) {
|
||||
log("tn: "+CommaSeparatedStringBuilder.join(",", tables.get(tn).getColumns()));
|
||||
}
|
||||
}
|
||||
private void logDataScheme(TableDataProvider tbl, Map<String, DataTable> tables) throws IOException {
|
||||
log("data: "+CommaSeparatedStringBuilder.join(",", tbl.columns()));
|
||||
for (String tn : Utilities.sorted(tables.keySet())) {
|
||||
log("tn: "+CommaSeparatedStringBuilder.join(",", tables.get(tn).getColumns()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void executeProfile() throws IOException {
|
||||
try {
|
||||
checkDownloadBaseData();
|
||||
|
||||
TableDataProvider tbl = loadTable(Utilities.path(rootFolder, details.asString( "data")));
|
||||
Map<String, DataTable> tables = new HashMap<>();
|
||||
if (details.has("tables")) {
|
||||
JsonObject tablesJ = details.getJsonObject("tables");
|
||||
for (String n : tablesJ.getNames()) {
|
||||
tables.put(n, loadData(Utilities.path(rootFolder, tablesJ.asString(n))));
|
||||
}
|
||||
}
|
||||
logDataScheme(tbl, tables);
|
||||
ProfileBasedFactory factory = new ProfileBasedFactory(fpe, localData.getAbsolutePath(), tbl, tables, details.forceArray("mappings"));
|
||||
factory.setLog(log);
|
||||
factory.setTesting(testing);
|
||||
String purl = details.asString( "profile");
|
||||
StructureDefinition profile = context.fetchResource(StructureDefinition.class, purl);
|
||||
if (profile == null) {
|
||||
error("Unable to find profile "+purl);
|
||||
} else if (!profile.hasSnapshot()) {
|
||||
error("Pprofile "+purl+" doesn't have a snapshot");
|
||||
}
|
||||
|
||||
if ("true".equals(details.asString("bundle"))) {
|
||||
byte[] data = runBundle(profile, factory, tbl);
|
||||
TextFile.bytesToFile(data, Utilities.path(rootFolder, details.asString( "filename")));
|
||||
} else {
|
||||
while (tbl.nextRow()) {
|
||||
if (rowPasses(factory)) {
|
||||
byte[] data = factory.generateFormat(profile, format);
|
||||
TextFile.bytesToFile(data, Utilities.path(rootFolder, getFileName(details.asString( "filename"), tbl.columns(), tbl.cells())));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error running test factory '"+getName()+"': "+e.getMessage());
|
||||
log("Error running test case '"+getName()+"': "+e.getMessage());
|
||||
e.printStackTrace(log);
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDownloadBaseData() throws IOException {
|
||||
localData = new File(Utilities.path("[tmp]", "fhir-test-data.db"));
|
||||
File localInfo = new File(Utilities.path("[tmp]", "fhir-test-data.json"));
|
||||
try {
|
||||
JsonObject local = localInfo.exists() ? JsonParser.parseObject(localInfo) : null;
|
||||
JsonObject json = JsonParser.parseObjectFromUrl("http://fhir.org/downloads/test-data-versions.json");
|
||||
JsonObject current = json.forceArray("versions").get(0).asJsonObject();
|
||||
if (current == null) {
|
||||
throw new FHIRException("No current information about FHIR downloads");
|
||||
}
|
||||
String date = current.asString("date");
|
||||
if (date == null) {
|
||||
throw new FHIRException("No date on current information about FHIR downloads");
|
||||
}
|
||||
String filename = current.asString("filename");
|
||||
if (filename == null) {
|
||||
throw new FHIRException("No filename on current information about FHIR downloads");
|
||||
}
|
||||
if (local == null || !date.equals(local.asString("date"))) {
|
||||
HTTPResult data = ManagedWebAccess.get(Utilities.strings("general"), "http://fhir.org/downloads/"+filename);
|
||||
TextFile.bytesToFile(data.getContent(), localData);
|
||||
local = new JsonObject();
|
||||
local.set("date", date);
|
||||
JsonParser.compose(current, localInfo, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (!localData.exists()) {
|
||||
log("Unable to download copy of FHIR testing data: "+ e.getMessage());
|
||||
throw new FHIRException("Unable to download copy of FHIR testing data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] runBundle(StructureDefinition profile, ProfileBasedFactory factory, TableDataProvider tbl) throws IOException, FHIRException, SQLException {
|
||||
Element bundle = Manager.parse(context, bundleShell(), FhirFormat.JSON).get(0).getElement();
|
||||
bundle.makeElement("id").setValue(UUID.randomUUID().toString().toLowerCase());
|
||||
|
||||
while (tbl.nextRow()) {
|
||||
if (rowPasses(factory)) {
|
||||
Element resource = factory.generate(profile);
|
||||
Element be = bundle.makeElement("entry");
|
||||
be.makeElement("fullUrl").setValue(Utilities.pathURL(canonical, "test", resource.fhirType(), resource.getIdBase()));
|
||||
be.makeElement("resource").getChildren().addAll(resource.getChildren());
|
||||
}
|
||||
}
|
||||
log("Saving Bundle");
|
||||
ByteArrayOutputStream bs = new ByteArrayOutputStream();
|
||||
Manager.compose(context, bundle, bs, format, OutputStyle.PRETTY, null);
|
||||
return bs.toByteArray();
|
||||
}
|
||||
|
||||
private boolean rowPasses(ProfileBasedFactory factory) throws IOException {
|
||||
if (details.has("filter")) {
|
||||
List<String> ls = new ArrayList<String>();
|
||||
String res = factory.evaluateExpression(ls, details.get("filter"), "filter");
|
||||
for (String l : ls) {
|
||||
log(l);
|
||||
}
|
||||
return Utilities.existsInList(res, "1", "true");
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private TableDataProvider loadTable(String path) throws IOException, InvalidFormatException {
|
||||
log("Load Data From "+path);
|
||||
return loadTableProvider(path);
|
||||
}
|
||||
|
||||
private void error(String msg) throws IOException {
|
||||
log(msg);
|
||||
log.close();
|
||||
throw new FHIRException(msg);
|
||||
}
|
||||
|
||||
private void log(String msg) throws IOException {
|
||||
log.append(msg+"\r\n");
|
||||
}
|
||||
|
||||
public void executeLiquid() throws IOException {
|
||||
try {
|
||||
LiquidDocument template = liquid.parse(TextFile.fileToString(Utilities.path(rootFolder, details.asString( "liquid"))), "liquid");
|
||||
log("liquid compiled");
|
||||
DataTable dt = loadData(Utilities.path(rootFolder, details.asString( "data")));
|
||||
Map<String, DataTable> tables = new HashMap<>();
|
||||
liquid.getVars().clear();
|
||||
if (details.has("tables")) {
|
||||
JsonObject tablesJ = details.getJsonObject("tables");
|
||||
for (String n : tablesJ.getNames()) {
|
||||
DataTable v = loadData(Utilities.path(rootFolder, tablesJ.asString(n)));
|
||||
liquid.getVars().put(n, v);
|
||||
tables.put(n, v);
|
||||
}
|
||||
}
|
||||
|
||||
logDataScheme(dt, tables);
|
||||
|
||||
logStrings("columns", dt.columns);
|
||||
if ("true".equals(details.asString( "bundle"))) {
|
||||
byte[] data = runBundle(template, dt);
|
||||
TextFile.bytesToFile(data, Utilities.path(rootFolder, details.asString( "filename")));
|
||||
} else {
|
||||
for (List<String> row : dt.rows) {
|
||||
byte[] data = runInstance(template, dt.columns, row);
|
||||
TextFile.bytesToFile(data, Utilities.path(rootFolder, getFileName(details.asString( "filename"), dt.columns, row)));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error running test factory '"+getName()+"': "+e.getMessage());
|
||||
log("Error running test case '"+getName()+"': "+e.getMessage());
|
||||
e.printStackTrace(log);
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logStrings(String name, List<String> columns) throws IOException {
|
||||
log(name+": "+CommaSeparatedStringBuilder.join(", ", columns));
|
||||
}
|
||||
|
||||
private String getFileName(String name, List<String> columns, List<String> values) {
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
name = name.replace("$"+columns.get(i)+"$", values.get(i));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private byte[] runInstance(LiquidDocument template, List<String> columns, List<String> row) throws JsonException, IOException {
|
||||
logStrings("row", row);
|
||||
BaseTableWrapper base = BaseTableWrapper.forRow(columns, row);
|
||||
String cnt = liquid.evaluate(template, base, this).trim();
|
||||
if (format == FhirFormat.JSON) {
|
||||
JsonObject j = JsonParser.parseObject(cnt, true);
|
||||
return JsonParser.composeBytes(j, true);
|
||||
} else {
|
||||
return TextFile.stringToBytes(cnt);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] runBundle(LiquidDocument template, DataTable dt) throws JsonException, IOException {
|
||||
Element bundle = Manager.parse(context, bundleShell(), FhirFormat.JSON).get(0).getElement();
|
||||
bundle.makeElement("id").setValue(UUID.randomUUID().toString().toLowerCase());
|
||||
|
||||
for (List<String> row : dt.rows) {
|
||||
byte[] data = runInstance(template, dt.columns, row);
|
||||
Element resource = Manager.parse(context, new ByteArrayInputStream(data), format).get(0).getElement();
|
||||
Element be = bundle.makeElement("entry");
|
||||
be.makeElement("fullUrl").setValue(Utilities.pathURL(canonical, "test", resource.fhirType(), resource.getIdBase()));
|
||||
be.makeElement("resource").getChildren().addAll(resource.getChildren());
|
||||
}
|
||||
log("Saving Bundle");
|
||||
ByteArrayOutputStream bs = new ByteArrayOutputStream();
|
||||
Manager.compose(context, bundle, bs, format, OutputStyle.PRETTY, null);
|
||||
return bs.toByteArray();
|
||||
}
|
||||
|
||||
private InputStream bundleShell() throws IOException {
|
||||
String bundle = "{\"resourceType\" : \"Bundle\", \"type\" : \"collection\"}";
|
||||
return new ByteArrayInputStream(TextFile.stringToBytes(bundle));
|
||||
}
|
||||
|
||||
private DataTable loadData(String path) throws FHIRException, IOException, InvalidFormatException {
|
||||
log("Load Data From "+path);
|
||||
TableDataProvider tbl = loadTableProvider(path);
|
||||
|
||||
DataTable dt = new DataTable();
|
||||
for (String n : tbl.columns()) {
|
||||
dt.columns.add(n);
|
||||
}
|
||||
int t = dt.columns.size();
|
||||
while (tbl.nextRow()) {
|
||||
List<String> values = new ArrayList<String>();
|
||||
for (String b : tbl.cells()) {
|
||||
values.add(b);
|
||||
}
|
||||
while (values.size() < t) {
|
||||
values.add("");
|
||||
}
|
||||
while (values.size() > t) {
|
||||
values.remove(values.size()-1);
|
||||
}
|
||||
dt.rows.add(values);
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
|
||||
public TableDataProvider loadTableProvider(String path) {
|
||||
TableDataProvider tbl;
|
||||
if (Utilities.isAbsoluteUrl(path)) {
|
||||
ValueSet vs = context.findTxResource(ValueSet.class, path);
|
||||
if (vs == null) {
|
||||
throw new FHIRException("ValueSet "+path+" not found");
|
||||
} else {
|
||||
org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome exp = context.expandVS(vs, true, false);
|
||||
if (exp.isOk()) {
|
||||
tbl = new ValueSetDataProvider(exp.getValueset().getExpansion());
|
||||
} else {
|
||||
throw new FHIRException("ValueSet "+path+" coult not be expanded: "+exp.getError());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tbl = TableDataProvider.forFile(path);
|
||||
}
|
||||
return tbl;
|
||||
}
|
||||
|
||||
public String statedLog() {
|
||||
return name+".log";
|
||||
}
|
||||
|
||||
public boolean isTesting() {
|
||||
return testing;
|
||||
}
|
||||
|
||||
public void setTesting(boolean testing) {
|
||||
this.testing = testing;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package org.hl7.fhir.r5.testfactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.PathEngineException;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
|
||||
import org.hl7.fhir.r5.fhirpath.TypeDetails;
|
||||
import org.hl7.fhir.r5.liquid.GlobalObject;
|
||||
import org.hl7.fhir.r5.model.Base;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
|
||||
public class TestDataHostServices implements IEvaluationContext {
|
||||
|
||||
private SimpleWorkerContext context;
|
||||
private DateTimeType dt;
|
||||
private StringType pathToSpec;
|
||||
private Map<String, FunctionDefinition> functions = new HashMap<>();
|
||||
|
||||
public TestDataHostServices(SimpleWorkerContext context, DateTimeType dt, StringType pathToSpec) {
|
||||
super();
|
||||
this.context = context;
|
||||
this.dt = dt;
|
||||
this.pathToSpec = pathToSpec;
|
||||
}
|
||||
|
||||
public TestDataHostServices registerFunction(FunctionDefinition function) {
|
||||
functions.put(function.name(), function);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
|
||||
if ("Globals".equals(name)) {
|
||||
List<Base> list = new ArrayList<Base>();
|
||||
list.add(new GlobalObject(dt, pathToSpec));
|
||||
return list;
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
|
||||
if ("Globals".equals(name)) {
|
||||
return new TypeDetails(CollectionStatus.SINGLETON, "GlobalObject");
|
||||
} else {
|
||||
return null; // whatever it is, we don't know about it.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean paramIsType(String name, int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean log(String argument, List<Base> focus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
|
||||
FunctionDefinition fd = functions.get(functionName);
|
||||
return fd == null ? null : fd.details();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List<TypeDetails> parameters) throws PathEngineException {
|
||||
FunctionDefinition fd = functions.get(functionName);
|
||||
return fd == null ? null : fd.check(engine, appContext, focus, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Base> executeFunction(FHIRPathEngine engine, Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
|
||||
FunctionDefinition fd = functions.get(functionName);
|
||||
return fd == null ? null : fd.execute(engine, appContext, focus, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException {
|
||||
throw new NotImplementedException("Not done yet (TestDataHostServices.conformsToProfile)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
|
||||
throw new NotImplementedException("Not done yet (TestDataHostServices.resolveValueSet)"); // cause I don't know when we 'd need to do this
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package org.hl7.fhir.r5.testfactory.dataprovider;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BaseDataTableProvider {
|
||||
private Connection connection;
|
||||
Map<String, String> elements = new HashMap<>();
|
||||
Map<String, String> types = new HashMap<>();
|
||||
private boolean testing;
|
||||
|
||||
public BaseDataTableProvider(String filename) throws SQLException {
|
||||
super();
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:"+filename);
|
||||
|
||||
ResultSet rs = connection.createStatement().executeQuery("select * from TestElements");
|
||||
while (rs.next()) {
|
||||
elements.put(rs.getString("ElementId"), rs.getString("ElementKey"));
|
||||
}
|
||||
|
||||
rs = connection.createStatement().executeQuery("select * from TestTypes");
|
||||
while (rs.next()) {
|
||||
types.put(rs.getString("TypeName"), rs.getString("TypeKey"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrimitiveValue(String elementId, String typeName) throws SQLException {
|
||||
String ek = elements.get(elementId);
|
||||
String tk = types.get(typeName);
|
||||
if (tk == null) {
|
||||
return null;
|
||||
}
|
||||
if (ek != null) {
|
||||
ResultSet rs = connection.createStatement().executeQuery("select ValueData from TestValues where ElementKey = "+ek+" and TypeKey = "+tk+(testing ? " ORDER BY ValueKey" : " ORDER BY RANDOM() LIMIT 1" ));
|
||||
if (rs.next()) {
|
||||
return rs.getString(1);
|
||||
}
|
||||
}
|
||||
ResultSet rs = connection.createStatement().executeQuery("select ValueData from TestValues where TypeKey = "+tk+(testing ? " ORDER BY ValueKey" : " ORDER BY RANDOM() LIMIT 1" ));
|
||||
if (rs.next()) {
|
||||
return rs.getString(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, String> getComplexValue(String elementId, String typeName) throws SQLException {
|
||||
String ek = elements.get(elementId);
|
||||
String tk = types.get(typeName);
|
||||
if (tk == null) {
|
||||
return null;
|
||||
}
|
||||
if (ek != null) {
|
||||
String sql = "select ValueData from TestValues where ElementKey = "+ek+" and TypeKey = "+tk+(testing ? " ORDER BY ValueKey" : " ORDER BY RANDOM() LIMIT 1" );
|
||||
ResultSet rs = connection.createStatement().executeQuery(sql);
|
||||
if (rs.next()) {
|
||||
return parse(rs.getString(1));
|
||||
}
|
||||
}
|
||||
String sql = "select ValueData from TestValues where TypeKey = "+tk+(testing ? " ORDER BY ValueKey" : " ORDER BY RANDOM() LIMIT 1" );
|
||||
ResultSet rs = connection.createStatement().executeQuery(sql);
|
||||
if (rs.next()) {
|
||||
return parse(rs.getString(1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, String> parse(String value) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
String[] parts = value.split("\\|\\:\\|");
|
||||
for (String p : parts) {
|
||||
String n = p.substring(0, p.indexOf(":"));
|
||||
String v = p.substring(p.indexOf(":")+1);
|
||||
|
||||
map.put(n.trim(), v.trim());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public boolean isTesting() {
|
||||
return testing;
|
||||
}
|
||||
|
||||
public void setTesting(boolean testing) {
|
||||
this.testing = testing;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.hl7.fhir.r5.testfactory.dataprovider;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.CSVReader;
|
||||
|
||||
public class CSVDataProvider extends TableDataProvider {
|
||||
|
||||
private CSVReader csv;
|
||||
private List<String> cols = new ArrayList<String>();
|
||||
private int counter;
|
||||
private String filename;
|
||||
|
||||
protected CSVDataProvider(String filename) throws FHIRException, FileNotFoundException, IOException {
|
||||
super();
|
||||
|
||||
this.filename = filename;
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> columns() {
|
||||
return cols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextRow() throws FHIRException {
|
||||
try {
|
||||
counter++;
|
||||
return csv.line();
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException("Error moving to next row: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cells() {
|
||||
List<String> cells = new ArrayList<String>();
|
||||
cells.add(""+counter);
|
||||
for (String s : csv.getCells()) {
|
||||
cells.add(s.trim());
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cell(String name) {
|
||||
if ("counter".equals(name)) {
|
||||
return ""+counter;
|
||||
} else {
|
||||
return csv.cell(name).trim();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws FHIRException {
|
||||
try {
|
||||
this.csv = new CSVReader(new FileInputStream(filename));
|
||||
|
||||
cols.add("counter");
|
||||
for (String s : csv.readHeaders()) {
|
||||
cols.add(s.trim());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error moving to next row: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
package org.hl7.fhir.r5.testfactory.dataprovider;
|
||||
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ExcelDataProvider extends TableDataProvider {
|
||||
|
||||
private Workbook workbook;
|
||||
private Sheet sheet;
|
||||
private List<String> columnHeaders;
|
||||
private int currentRowIndex = -1;
|
||||
private Map<String, Integer> columnIndexMap = new HashMap<>();
|
||||
private Row currentRow;
|
||||
private DataFormatter df = new DataFormatter();
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an ExcelTableDataProvider.
|
||||
*
|
||||
* @param filename The path to the Excel file.
|
||||
* @param sheetName The name of the sheet to read.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
* @throws InvalidFormatException If the file format is invalid.
|
||||
*/
|
||||
public ExcelDataProvider(String filename, String sheetName) throws IOException, InvalidFormatException {
|
||||
FileInputStream fis = new FileInputStream(new File(filename));
|
||||
this.workbook = WorkbookFactory.create(fis);
|
||||
if (sheetName != null) {
|
||||
this.sheet = workbook.getSheet(sheetName);
|
||||
}
|
||||
|
||||
if (sheet == null) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
|
||||
names.add(workbook.getSheetName(i));
|
||||
}
|
||||
if (sheetName == null && names.size() > 0) {
|
||||
this.sheet = workbook.getSheet(names.get(0));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Sheet '" + sheetName + "' does not exist in the file. Sheet Names = "+CommaSeparatedStringBuilder.join(",", names));
|
||||
}
|
||||
}
|
||||
|
||||
loadColumnHeaders();
|
||||
}
|
||||
|
||||
|
||||
public ExcelDataProvider(String filename) throws InvalidFormatException, IOException {
|
||||
FileInputStream fis = new FileInputStream(new File(filename));
|
||||
this.workbook = WorkbookFactory.create(fis);
|
||||
this.sheet = workbook.getSheetAt(0);
|
||||
loadColumnHeaders();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the column headers from the first row of the sheet.
|
||||
*/
|
||||
private void loadColumnHeaders() {
|
||||
columnHeaders = new ArrayList<>();
|
||||
columnHeaders.add("counter");
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow != null) {
|
||||
for (Cell cell : headerRow) {
|
||||
String headerName = cell.getStringCellValue().trim();
|
||||
columnHeaders.add(headerName);
|
||||
columnIndexMap.put(headerName, cell.getColumnIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> columns() {
|
||||
return columnHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextRow() {
|
||||
currentRowIndex++;
|
||||
currentRow = sheet.getRow(currentRowIndex + 1); // Skip the header row
|
||||
return currentRow != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cells() {
|
||||
List<String> cellValues = new ArrayList<>();
|
||||
cellValues.add(""+(currentRowIndex+1));
|
||||
if (currentRow != null) {
|
||||
for (Cell cell : currentRow) {
|
||||
int i = cell.getColumnIndex();
|
||||
while (cellValues.size() <= i) {
|
||||
cellValues.add("");
|
||||
}
|
||||
cellValues.add(getCellValue(cell).trim());
|
||||
}
|
||||
}
|
||||
return cellValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cell(String name) {
|
||||
if ("counter".equals(name)) {
|
||||
return ""+currentRowIndex;
|
||||
} else {
|
||||
Integer columnIndex = columnIndexMap.get(name);
|
||||
if (columnIndex == null || currentRow == null) {
|
||||
return null;
|
||||
}
|
||||
Cell cell = currentRow.getCell(columnIndex);
|
||||
return cell == null ? null : getCellValue(cell).trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to get a cell value as a string.
|
||||
*
|
||||
* @param cell The cell.
|
||||
* @return The cell value as a string.
|
||||
*/
|
||||
private String getCellValue(Cell cell) {
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
return df.formatCellValue(cell);
|
||||
case BOOLEAN:
|
||||
return Boolean.toString(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
return cell.getCellFormula();
|
||||
case BLANK:
|
||||
return "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the workbook and releases resources.
|
||||
*
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (workbook != null) {
|
||||
workbook.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws FHIRException {
|
||||
currentRowIndex = -1;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package org.hl7.fhir.r5.testfactory.dataprovider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
/**
|
||||
* Concrete implementation of TableDataProvider that reads data from a SQL database table.
|
||||
*/
|
||||
public class SQLDataProvider extends TableDataProvider {
|
||||
|
||||
private Connection connection;
|
||||
private String tableName;
|
||||
private List<String> columnHeaders;
|
||||
private ResultSet resultSet;
|
||||
private ResultSetMetaData metaData;
|
||||
private Map<String, Integer> columnIndexMap = new HashMap<>();
|
||||
private int counter;
|
||||
|
||||
/**
|
||||
* Constructs an SQLDataProvider.
|
||||
*
|
||||
* @param connection The SQL database connection.
|
||||
* @param tableName The name of the table to read.
|
||||
* @throws IOException If a database access error occurs.
|
||||
*/
|
||||
public SQLDataProvider(Connection connection, String tableName) throws IOException {
|
||||
this.connection = connection;
|
||||
this.tableName = tableName;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the column headers from the table's metadata.
|
||||
*
|
||||
* @throws IOException If a database access error occurs.
|
||||
*/
|
||||
private void loadColumnHeaders() throws IOException {
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet rs = statement.executeQuery("SELECT * FROM " + tableName + " WHERE 1=0")) {
|
||||
metaData = rs.getMetaData();
|
||||
columnHeaders = new ArrayList<>();
|
||||
columnHeaders.add("counter");
|
||||
for (int i = 1; i <= metaData.getColumnCount(); i++) {
|
||||
String columnName = metaData.getColumnName(i);
|
||||
columnHeaders.add(columnName);
|
||||
columnIndexMap.put(columnName, i);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new FHIRException("Error loading column headers: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the ResultSet for iterating over the table's rows.
|
||||
*
|
||||
* @throws IOException If a database access error occurs.
|
||||
*/
|
||||
private void prepareResultSet() throws IOException {
|
||||
try {
|
||||
Statement statement = connection.createStatement();
|
||||
resultSet = statement.executeQuery("SELECT * FROM " + tableName);
|
||||
} catch (SQLException e) {
|
||||
throw new IOException("Error preparing result set: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> columns() {
|
||||
return columnHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextRow() throws FHIRException {
|
||||
try {
|
||||
counter++;
|
||||
return resultSet.next();
|
||||
} catch (SQLException e) {
|
||||
throw new FHIRException("Error moving to next row: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cells() throws FHIRException {
|
||||
try {
|
||||
List<String> cellValues = new ArrayList<>();
|
||||
cellValues.add(""+counter);
|
||||
for (int i = 1; i <= metaData.getColumnCount(); i++) {
|
||||
cellValues.add(resultSet.getString(i));
|
||||
}
|
||||
return cellValues;
|
||||
} catch (SQLException e) {
|
||||
throw new FHIRException("Error retrieving row cells: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cell(String name) throws FHIRException {
|
||||
if ("counter".equals(name)) {
|
||||
return ""+counter;
|
||||
} else {
|
||||
try {
|
||||
Integer columnIndex = columnIndexMap.get(name);
|
||||
if (columnIndex == null) {
|
||||
return null;
|
||||
}
|
||||
return resultSet.getString(columnIndex);
|
||||
} catch (SQLException e) {
|
||||
throw new FHIRException("Error retrieving cell value: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the ResultSet and releases the database resources.
|
||||
*
|
||||
* @throws IOException If a database access error occurs.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
if (resultSet != null) {
|
||||
resultSet.close();
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IOException("Error closing resources: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws FHIRException {
|
||||
try {
|
||||
loadColumnHeaders();
|
||||
prepareResultSet();
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException("Error closing resources: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package org.hl7.fhir.r5.testfactory.dataprovider;
|
||||
|
||||
import java.sql.DriverManager;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
public abstract class TableDataProvider {
|
||||
|
||||
public abstract List<String> columns();
|
||||
public abstract boolean nextRow() throws FHIRException;
|
||||
public abstract List<String> cells() throws FHIRException;
|
||||
public abstract String cell(String name) throws FHIRException;
|
||||
public abstract void reset() throws FHIRException;
|
||||
|
||||
|
||||
public static TableDataProvider forFile(String path) throws FHIRException {
|
||||
try {
|
||||
String filename = path;
|
||||
String sheetname = null;
|
||||
|
||||
if (path.contains(";")) {
|
||||
filename = path.substring(0, path.indexOf(";"));
|
||||
sheetname = path.substring(path.indexOf(";")+1);
|
||||
}
|
||||
String extension = Utilities.getFileExtension(filename);
|
||||
if (Utilities.existsInList(extension, "csv", "txt")) {
|
||||
return new CSVDataProvider(filename);
|
||||
} else if (Utilities.existsInList(extension, "xlsx")) {
|
||||
return new ExcelDataProvider(filename, sheetname);
|
||||
} else if (Utilities.existsInList(extension, "db")) {
|
||||
return new SQLDataProvider(DriverManager.getConnection("jdbc:sqlite:"+filename), sheetname);
|
||||
} else {
|
||||
throw new FHIRException("Unknown File Type "+path);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package org.hl7.fhir.r5.testfactory.dataprovider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
|
||||
public class ValueSetDataProvider extends TableDataProvider {
|
||||
|
||||
public enum ColumnSpecifierType {
|
||||
COUNTER, SYSTEM, VERSION, CODE, DISPLAY, ABSTRACT, INACTIVE, PROPERTY
|
||||
|
||||
}
|
||||
|
||||
public class ColumnSpecifier {
|
||||
|
||||
private String name;
|
||||
private ColumnSpecifierType type;
|
||||
private String details;
|
||||
|
||||
public ColumnSpecifier(String name, ColumnSpecifierType type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public ColumnSpecifier(String name, ColumnSpecifierType type, String details) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ValueSetExpansionComponent expansion;
|
||||
private List<ColumnSpecifier> columns;
|
||||
private List<String> columnNames;
|
||||
private int cursor;
|
||||
|
||||
public ValueSetDataProvider(ValueSetExpansionComponent expansion) {
|
||||
super();
|
||||
this.expansion = expansion;
|
||||
process();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void process() {
|
||||
columns = new ArrayList<ColumnSpecifier>();
|
||||
columns.add(new ColumnSpecifier("counter", ColumnSpecifierType.COUNTER));
|
||||
columns.add(new ColumnSpecifier("system", ColumnSpecifierType.SYSTEM));
|
||||
columns.add(new ColumnSpecifier("version", ColumnSpecifierType.VERSION));
|
||||
columns.add(new ColumnSpecifier("code", ColumnSpecifierType.CODE));
|
||||
columns.add(new ColumnSpecifier("display", ColumnSpecifierType.DISPLAY));
|
||||
columns.add(new ColumnSpecifier("abstract", ColumnSpecifierType.ABSTRACT));
|
||||
columns.add(new ColumnSpecifier("inactive", ColumnSpecifierType.INACTIVE));
|
||||
for (ValueSetExpansionContainsComponent cc : expansion.getContains()) {
|
||||
// for (ConceptReferenceDesignationComponent d : cc.getDesignation()) {
|
||||
// if (!hasColumn(d.getLanguage(), d.getUse())) {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
for (ConceptPropertyComponent p : cc.getProperty()) {
|
||||
if (!hasColumn(ColumnSpecifierType.PROPERTY, p.getCode()) ) {
|
||||
columns.add(new ColumnSpecifier("property."+p.getCode(), ColumnSpecifierType.PROPERTY, p.getCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
columnNames = new ArrayList<>();
|
||||
for (ColumnSpecifier sp : columns) {
|
||||
columnNames.add(sp.name);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasColumn(ColumnSpecifierType type, String details) {
|
||||
for (ColumnSpecifier sp : columns) {
|
||||
if (sp.type == type && details.equals(sp.details)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> columns() {
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextRow() throws FHIRException {
|
||||
cursor++;
|
||||
return cursor < expansion.getContains().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cells() throws FHIRException {
|
||||
List<String> cells = new ArrayList<>();
|
||||
ValueSetExpansionContainsComponent cc = expansion.getContains().get(cursor);
|
||||
for (ColumnSpecifier sp : columns) {
|
||||
switch (sp.type) {
|
||||
case ABSTRACT:
|
||||
cells.add(""+cc.getAbstract());
|
||||
break;
|
||||
case CODE:
|
||||
cells.add(cc.getCode());
|
||||
break;
|
||||
case COUNTER:
|
||||
cells.add(""+cursor);
|
||||
break;
|
||||
case DISPLAY:
|
||||
cells.add(cc.getDisplay());
|
||||
break;
|
||||
case INACTIVE:
|
||||
cells.add(""+cc.getInactive());
|
||||
break;
|
||||
case PROPERTY:
|
||||
for (ConceptPropertyComponent p : cc.getProperty()) {
|
||||
if (sp.details.equals(p.getCode())) {
|
||||
cells.add(p.getValue().primitiveValue());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SYSTEM:
|
||||
cells.add(cc.getSystem());
|
||||
break;
|
||||
case VERSION:
|
||||
cells.add(cc.getVersion());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cell(String name) throws FHIRException {
|
||||
int i = columns.indexOf(name);
|
||||
return i == -1 ? null : cells().get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws FHIRException {
|
||||
cursor = -1;
|
||||
}
|
||||
|
||||
}
|
@ -102,7 +102,7 @@ public class PETests {
|
||||
checkElement(children.get(2), "language", "language", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/code", 2, "language");
|
||||
checkElement(children.get(3), "text", "text", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/Narrative", 3, "text");
|
||||
checkElement(children.get(4), "contained", "contained", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Resource", 4, "contained");
|
||||
checkElement(children.get(5), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension.where(((url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-simple') or (url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')).not())");
|
||||
checkElement(children.get(5), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension.where(((url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-simple') or (url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')).not())");
|
||||
checkElement(children.get(6), "extension", "simple", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/code", 2, "extension('http://hl7.org/fhir/test/StructureDefinition/pe-extension-simple').value");
|
||||
checkElement(children.get(7), "extension", "complex", 0, 1, false, "http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex", 4, "extension('http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')");
|
||||
checkElement(children.get(8), "identifier", "identifier", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/Identifier", 7, "identifier");
|
||||
@ -117,7 +117,7 @@ public class PETests {
|
||||
checkElement(children.get(17), "value[x]", "valueCodeableConcept", 0, 1, false, "http://hl7.org/fhir/test/StructureDefinition/pe-profile2", 4, "value.ofType(CodeableConcept)");
|
||||
|
||||
List<PEDefinition> gchildren = children.get(11).children();
|
||||
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, true, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension");
|
||||
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, true, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension");
|
||||
checkElement(gchildren.get(1), "coding", "coding", 0, Integer.MAX_VALUE, true, "http://hl7.org/fhir/StructureDefinition/Coding", 6, "coding");
|
||||
checkElement(gchildren.get(2), "text", "text", 0, 1, true, "http://hl7.org/fhir/StructureDefinition/string", 2, "text");
|
||||
|
||||
@ -129,17 +129,17 @@ public class PETests {
|
||||
checkElement(gchildren.get(3), "text", "text", 1, 1, false, "http://hl7.org/fhir/StructureDefinition/string", 2, "text");
|
||||
|
||||
List<PEDefinition> ggchildren = gchildren.get(3).children("http://hl7.org/fhir/StructureDefinition/string");
|
||||
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension");
|
||||
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension");
|
||||
checkElement(ggchildren.get(1), "value", "value", 1, 1, false, null, 3, "value");
|
||||
|
||||
gchildren = children.get(7).children("http://hl7.org/fhir/StructureDefinition/Extension");
|
||||
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension.where(((url = 'slice1') or (url = 'slice2') or (url = 'slice3')).not())");
|
||||
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension.where(((url = 'slice1') or (url = 'slice2') or (url = 'slice3')).not())");
|
||||
checkElement(gchildren.get(1), "extension", "slice1", 0, 2, false, "http://hl7.org/fhir/StructureDefinition/Coding", 6, "extension('slice1').value");
|
||||
checkElement(gchildren.get(2), "extension", "slice2", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/string", 2, "extension('slice2').value");
|
||||
checkElement(gchildren.get(3), "extension", "slice3", 1, 1, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension('slice3')");
|
||||
|
||||
ggchildren = gchildren.get(3).children("http://hl7.org/fhir/StructureDefinition/Extension");
|
||||
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension");
|
||||
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension");
|
||||
checkElement(ggchildren.get(1), "extension", "slice3a", 0, 2, false, "http://hl7.org/fhir/StructureDefinition/Coding", 6, "extension('slice3a').value");
|
||||
checkElement(ggchildren.get(2), "extension", "slice3b", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/string", 2, "extension('slice3b').value");
|
||||
}
|
||||
@ -178,7 +178,7 @@ public class PETests {
|
||||
Assertions.assertEquals("\\-", pe.documentation());
|
||||
|
||||
List<PEDefinition> children = pe.children("Patient");
|
||||
Assertions.assertEquals(26, children.size());
|
||||
Assertions.assertEquals(28, children.size());
|
||||
|
||||
pe = children.get(9);
|
||||
Assertions.assertEquals("birthsex", pe.name());
|
||||
@ -270,7 +270,7 @@ public class PETests {
|
||||
Assertions.assertEquals("May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and managable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.", pe.documentation());
|
||||
|
||||
iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Extension");
|
||||
Assertions.assertEquals(3, iChildren.size());
|
||||
Assertions.assertEquals(4, iChildren.size());
|
||||
pe = iChildren.get(1);
|
||||
Assertions.assertEquals("extension", pe.name());
|
||||
Assertions.assertEquals("extension", pe.schemaName());
|
||||
@ -284,7 +284,7 @@ public class PETests {
|
||||
|
||||
|
||||
iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Extension");
|
||||
Assertions.assertEquals(3, iChildren.size());
|
||||
Assertions.assertEquals(4, iChildren.size());
|
||||
pe = iChildren.get(1);
|
||||
Assertions.assertEquals("extension", pe.name());
|
||||
Assertions.assertEquals("extension", pe.schemaName());
|
||||
|
@ -63,8 +63,9 @@ public class CSVReader extends InputStreamReader {
|
||||
private boolean multiline;
|
||||
private boolean doingQuotes = true;
|
||||
|
||||
public void readHeaders() throws IOException, FHIRException {
|
||||
public String[] readHeaders() throws IOException, FHIRException {
|
||||
cols = parseLine();
|
||||
return cols;
|
||||
}
|
||||
|
||||
public boolean line() throws IOException, FHIRException {
|
||||
@ -248,5 +249,9 @@ public class CSVReader extends InputStreamReader {
|
||||
this.doingQuotes = doingQuotes;
|
||||
}
|
||||
|
||||
public String[] getCells() {
|
||||
return cells;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -29,6 +29,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
@ -2405,4 +2407,15 @@ public class Utilities {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String extractByRegex(String input, String regex) {
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
result.append(matcher.group(1));
|
||||
}
|
||||
return result.length() == 0 ? null : result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -164,6 +164,39 @@ public class NpmPackage {
|
||||
}
|
||||
}
|
||||
|
||||
public static class PackagedResourceFile {
|
||||
private String folder;
|
||||
private String filename;
|
||||
private String resourceType;
|
||||
protected PackagedResourceFile(String folder, String filename, String resourceType) {
|
||||
super();
|
||||
this.folder = folder;
|
||||
this.filename = filename;
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
public String getFolder() {
|
||||
return folder;
|
||||
}
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
public static class Sorter implements Comparator<PackagedResourceFile> {
|
||||
|
||||
@Override
|
||||
public int compare(PackagedResourceFile o1, PackagedResourceFile o2) {
|
||||
int res = o1.folder.compareTo(o2.folder);
|
||||
if (res == 0) {
|
||||
res = o1.filename.compareTo(o2.filename);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValidName(String pid) {
|
||||
return pid.matches("^[a-z][a-zA-Z0-9]*(\\.[a-z][a-zA-Z0-9\\-]*)+$");
|
||||
}
|
||||
@ -780,14 +813,14 @@ public class NpmPackage {
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<StringPair> listAllResources(List<String> types) throws IOException {
|
||||
List<StringPair> res = new ArrayList<StringPair>();
|
||||
public List<PackagedResourceFile> listAllResources(Collection<String> types) throws IOException {
|
||||
List<PackagedResourceFile> res = new ArrayList<PackagedResourceFile>();
|
||||
for (NpmPackageFolder folder : folders.values()) {
|
||||
if (types.size() == 0) {
|
||||
for (String s : folder.types.keySet()) {
|
||||
if (folder.types.containsKey(s)) {
|
||||
for (String n : folder.types.get(s)) {
|
||||
res.add(new StringPair(folder.folderName, n));
|
||||
res.add(new PackagedResourceFile(folder.folderName, n, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -795,13 +828,13 @@ public class NpmPackage {
|
||||
for (String s : types) {
|
||||
if (folder.types.containsKey(s)) {
|
||||
for (String n : folder.types.get(s)) {
|
||||
res.add(new StringPair(folder.folderName, n));
|
||||
res.add(new PackagedResourceFile(folder.folderName, n, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(res, new StringPair.Sorter());
|
||||
Collections.sort(res, new PackagedResourceFile.Sorter());
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -326,8 +326,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||
case "slice": return new FunctionDetails("Returns the given slice as defined in the given structure definition. If in an invariant, First parameter can be %profile - current profile", 2, 2);
|
||||
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;
|
||||
}
|
||||
default:
|
||||
if (externalHostServices != null) {
|
||||
return externalHostServices.resolveFunction(engine, functionName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -340,7 +345,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||
|
||||
case "getResourceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string");
|
||||
case "getReferenceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string");
|
||||
default: throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCHECKFUNCTION));
|
||||
default:
|
||||
if (externalHostServices != null) {
|
||||
return externalHostServices.checkFunction(engine, appContext, functionName, focus, parameters);
|
||||
} else {
|
||||
throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCHECKFUNCTION));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +359,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||
switch (functionName) {
|
||||
case "slice": return executeSlice(engine, appContext, focus, parameters);case "getResourceKey" : return executeResourceKey(focus);
|
||||
case "getReferenceKey" : return executeReferenceKey(null, focus, parameters);
|
||||
default: throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESEXECUTEFUNCTION));
|
||||
default:
|
||||
if (externalHostServices != null) {
|
||||
return externalHostServices.executeFunction(engine, appContext, focus, functionName, parameters);
|
||||
} else {
|
||||
throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESEXECUTEFUNCTION));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,32 +1,103 @@
|
||||
package org.hl7.fhir.generation.tests;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.NullLoaderKnowledgeProviderR5;
|
||||
import org.hl7.fhir.convertors.loaders.loaderR5.R4ToR5Loader;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext;
|
||||
import org.hl7.fhir.r5.context.SimpleWorkerContext.SimpleWorkerContextBuilder;
|
||||
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
|
||||
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.liquid.BaseTableWrapper;
|
||||
import org.hl7.fhir.r5.liquid.LiquidEngine;
|
||||
import org.hl7.fhir.r5.liquid.GlobalObject.GlobalObjectRandomFunction;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.profilemodel.TestInstanceGenerator;
|
||||
import org.hl7.fhir.r5.test.utils.CompareUtilities;
|
||||
import org.hl7.fhir.r5.test.utils.TestingUtilities;
|
||||
import org.hl7.fhir.r5.testfactory.ProfileBasedFactory;
|
||||
import org.hl7.fhir.r5.testfactory.TestDataFactory;
|
||||
import org.hl7.fhir.r5.testfactory.TestDataHostServices;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.json.model.JsonObject;
|
||||
import org.hl7.fhir.utilities.json.parser.JsonParser;
|
||||
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TestInstanceGenerationTester {
|
||||
|
||||
@Test
|
||||
public void testBasic() throws IOException {
|
||||
public void testDataFactory() throws IOException, FHIRException, SQLException {
|
||||
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build();
|
||||
SimpleWorkerContext context = new SimpleWorkerContextBuilder().withAllowLoadingDuplicates(true).withDefaultParams().fromPackage(pcm.loadPackage("hl7.fhir.r4.core"));
|
||||
context.loadFromPackage(pcm.loadPackage("us.nlm.vsac#0.21.0"), new R4ToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"),
|
||||
new NullLoaderKnowledgeProviderR5(), context.getVersion()));
|
||||
context.loadFromPackage(pcm.loadPackage("hl7.fhir.us.core#6.0.0"), new R4ToR5Loader(Utilities.strings("CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"),
|
||||
new NullLoaderKnowledgeProviderR5(), context.getVersion()));
|
||||
|
||||
FHIRPathEngine fpe = new FHIRPathEngine(context);
|
||||
TestDataHostServices hs = new TestDataHostServices(context, new DateTimeType("2024-12-24T09:01:00+11:00"), new StringType("http://hl7.org/fhir"));
|
||||
hs.registerFunction(new GlobalObjectRandomFunction());
|
||||
hs.registerFunction(new BaseTableWrapper.TableColumnFunction());
|
||||
hs.registerFunction(new BaseTableWrapper.TableDateColumnFunction());
|
||||
hs.registerFunction(new TestDataFactory.CellLookupFunction());
|
||||
hs.registerFunction(new TestDataFactory.TableLookupFunction());
|
||||
fpe.setHostServices(hs);
|
||||
LiquidEngine liquid = new LiquidEngine(context, hs);
|
||||
|
||||
TestInstanceGenerator gen = new TestInstanceGenerator(context);
|
||||
byte[] output = gen.generate(context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"), FhirFormat.JSON);
|
||||
String res = new String(output);
|
||||
System.out.println(res);
|
||||
StructureDefinition sd = (StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(TestingUtilities.loadTestResourceBytes("rX", "instance-generation", "collateral", "StructureDefinition-test-patient-profile.json"));
|
||||
context.cacheResource(sd);
|
||||
|
||||
// set up the space
|
||||
String path = Utilities.path("[tmp]", "instance-generation");
|
||||
Utilities.createDirectory(path);
|
||||
Utilities.clearDirectory(path);
|
||||
|
||||
String log = Utilities.path(path, "log");
|
||||
Utilities.createDirectory(log);
|
||||
|
||||
String output = Utilities.path(path, "output");
|
||||
Utilities.createDirectory(output);
|
||||
|
||||
String expected = Utilities.path(path, "expected");
|
||||
Utilities.createDirectory(expected);
|
||||
|
||||
|
||||
for (String name : Utilities.strings("countries.csv", "data.csv", "encounter.liquid", "factories.json", "patient-cases.xlsx", "patient-genders.csv", "patient.liquid", "test-cases.liquid")) {
|
||||
byte[] fsrc = TestingUtilities.loadTestResourceBytes("rX", "instance-generation", "factories", name);
|
||||
TextFile.bytesToFile(fsrc, Utilities.path(path, name));
|
||||
}
|
||||
for (String name : Utilities.strings("Patient-1.json", "Encounter-1.json", "MedicationStatement-1.json", "Observation-bp-1.json", "Observation-weight-1.json")) {
|
||||
byte[] fsrc = TestingUtilities.loadTestResourceBytes("rX", "instance-generation", "expected", name);
|
||||
TextFile.bytesToFile(fsrc, Utilities.path(path, "expected", name));
|
||||
}
|
||||
JsonObject json = JsonParser.parseObjectFromFile(Utilities.path(path, "factories.json"));
|
||||
for (JsonObject fact : json.forceArray("factories").asJsonObjects()) {
|
||||
TestDataFactory tdf = new TestDataFactory(context, fact, liquid, fpe, "http://hl7.org/fhir/test", path, log);
|
||||
tdf.setTesting(true); // no randomness
|
||||
System.out.println("Execute Test Data Factory '"+tdf.getName()+"'. Output in "+tdf.statedLog());
|
||||
tdf.execute();
|
||||
}
|
||||
|
||||
// now, check output
|
||||
for (String name : Utilities.strings("Bundle-patients.json", "Encounter-1.json", "Encounter-2.json", "Encounter-3.json", "Encounter-4.json", "MedicationStatement-1.json",
|
||||
"MedicationStatement-2.json", "MedicationStatement-4.json", "Observation-bp-1.json", "Observation-bp-2.json", "Observation-bp-3.json",
|
||||
"Observation-bp-4.json", "Observation-weight-1.json", "Observation-weight-2.json", "Observation-weight-3.json", "Observation-weight-4.json",
|
||||
"Patient-1.json", "Patient-2.json", "Patient-3.json", "Patient-4.json")) {
|
||||
File f = new File(Utilities.path(output, name));
|
||||
Assertions.assertTrue(f.exists());
|
||||
}
|
||||
|
||||
for (String name : Utilities.strings("Patient-1.json", "Encounter-1.json", "MedicationStatement-1.json", "Observation-bp-1.json", "Observation-weight-1.json")) {
|
||||
String diff = new CompareUtilities(null, null).checkJsonSrcIsSame(name, TextFile.fileToString(Utilities.path(output, name)), TextFile.fileToString(Utilities.path(expected, name)), false);
|
||||
Assertions.assertNull(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user