Clone SQL on FHIR engine to R4, and update FHIRPath engine based on R5 current code

This commit is contained in:
Grahame Grieve 2024-11-01 08:05:43 +10:30
parent d883dd37c2
commit d58188748c
28 changed files with 5444 additions and 2673 deletions

View File

@ -282,6 +282,8 @@ public class ProfileUtilities extends TranslatingUtilities {
public String url;
}
public boolean isPrimitiveType(String typeSimple);
public boolean isDatatype(String typeSimple);
public boolean isResource(String typeSimple);

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.r4.context;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@ -57,6 +58,9 @@ import org.hl7.fhir.r4.terminologies.ValueSetExpander.TerminologyServiceErrorCla
import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.r4.utils.ToolingExtensions;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.utilities.OIDUtils;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.Utilities;
@ -894,6 +898,22 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
}
@Override
public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source) {
return fetchResource(class_, uri);
}
@Override
public List<StructureDefinition> fetchTypeDefinitions(String n) {
List<StructureDefinition> types = new ArrayList<>();
for (StructureDefinition sd : fetchResourcesByType(StructureDefinition.class)) {
if (n.equals(sd.getTypeTail())) {
types.add(sd);
}
}
return types;
}
public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version) {
try {
return fetchResourceWithException(class_, uri+"|"+version);
@ -1250,4 +1270,51 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return corePath + "snomed.html";
return null;
}
@SuppressWarnings("unchecked")
public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_) {
List<T> res = new ArrayList<>();
synchronized (lock) {
if (class_ == Resource.class || class_ == DomainResource.class || class_ == null) {
res.addAll((Collection<T>) structures.values());
res.addAll((Collection<T>) guides.values());
res.addAll((Collection<T>) capstmts.values());
res.addAll((Collection<T>) valueSets.values());
res.addAll((Collection<T>) codeSystems.values());
res.addAll((Collection<T>) operations.values());
res.addAll((Collection<T>) searchParameters.values());
res.addAll((Collection<T>) plans.values());
res.addAll((Collection<T>) maps.values());
res.addAll((Collection<T>) transforms.values());
res.addAll((Collection<T>) questionnaires.values());
} else if (class_ == ImplementationGuide.class) {
res.addAll((Collection<T>) guides.values());
} else if (class_ == CapabilityStatement.class) {
res.addAll((Collection<T>) capstmts.values());
} else if (class_ == StructureDefinition.class) {
res.addAll((Collection<T>) structures.values());
} else if (class_ == StructureMap.class) {
res.addAll((Collection<T>) transforms.values());
} else if (class_ == ValueSet.class) {
res.addAll((Collection<T>) valueSets.values());
} else if (class_ == CodeSystem.class) {
res.addAll((Collection<T>) codeSystems.values());
} else if (class_ == ConceptMap.class) {
res.addAll((Collection<T>) maps.values());
} else if (class_ == PlanDefinition.class) {
res.addAll((Collection<T>) plans.values());
} else if (class_ == OperationDefinition.class) {
res.addAll((Collection<T>) operations.values());
} else if (class_ == Questionnaire.class) {
res.addAll((Collection<T>) questionnaires.values());
} else if (class_ == SearchParameter.class) {
res.addAll((Collection<T>) searchParameters.values());
}
}
return res;
}
}

View File

@ -0,0 +1,461 @@
package org.hl7.fhir.r4.context;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.conformance.ProfileUtilities;
import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4.model.NamingSystem.NamingSystemIdentifierType;
import org.hl7.fhir.r4.model.NamingSystem.NamingSystemUniqueIdComponent;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4.model.StructureMap;
import org.hl7.fhir.r4.utils.ToolingExtensions;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.NamingSystem;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.utilities.OIDUtils;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public class ContextUtilities implements ProfileKnowledgeProvider {
private IWorkerContext context;
private boolean suppressDebugMessages;
private Map<String, String> oidCache = new HashMap<>();
private List<StructureDefinition> allStructuresList = new ArrayList<StructureDefinition>();
private List<String> canonicalResourceNames;
private List<String> concreteResourceNames;
private Set<String> concreteResourceNameSet;
public ContextUtilities(IWorkerContext context) {
super();
this.context = context;
}
public boolean isSuppressDebugMessages() {
return suppressDebugMessages;
}
public void setSuppressDebugMessages(boolean suppressDebugMessages) {
this.suppressDebugMessages = suppressDebugMessages;
}
public String oid2Uri(String oid) {
if (oid != null && oid.startsWith("urn:oid:")) {
oid = oid.substring(8);
}
if (oidCache.containsKey(oid)) {
return oidCache.get(oid);
}
String uri = OIDUtils.getUriForOid(oid);
if (uri != null) {
oidCache.put(oid, uri);
return uri;
}
CodeSystem cs = context.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-tables");
if (cs != null) {
for (ConceptDefinitionComponent cc : cs.getConcept()) {
for (ConceptPropertyComponent cp : cc.getProperty()) {
if (Utilities.existsInList(cp.getCode(), "v2-table-oid", "v2-cs-oid") && oid.equals(cp.getValue().primitiveValue())) {
for (ConceptPropertyComponent cp2 : cc.getProperty()) {
if ("v2-cs-uri".equals(cp2.getCode())) {
oidCache.put(oid, cp2.getValue().primitiveValue());
return cp2.getValue().primitiveValue();
}
}
}
}
}
}
for (CodeSystem css : context.fetchResourcesByType(CodeSystem.class)) {
if (("urn:oid:"+oid).equals(css.getUrl())) {
oidCache.put(oid, css.getUrl());
return css.getUrl();
}
for (Identifier id : css.getIdentifier()) {
if ("urn:ietf:rfc:3986".equals(id.getSystem()) && ("urn:oid:"+oid).equals(id.getValue())) {
oidCache.put(oid, css.getUrl());
return css.getUrl();
}
}
}
for (NamingSystem ns : context.fetchResourcesByType(NamingSystem.class)) {
if (hasOid(ns, oid)) {
uri = getUri(ns);
if (uri != null) {
oidCache.put(oid, null);
return null;
}
}
}
oidCache.put(oid, null);
return null;
}
private String getUri(NamingSystem ns) {
for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
if (id.getType() == NamingSystemIdentifierType.URI)
return id.getValue();
}
return null;
}
private boolean hasOid(NamingSystem ns, String oid) {
for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) {
if (id.getType() == NamingSystemIdentifierType.OID && id.getValue().equals(oid))
return true;
}
return false;
}
/**
* @return a list of the resource and type names defined for this version
*/
public List<String> getTypeNames() {
Set<String> result = new HashSet<String>();
for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
result.add(sd.getName());
}
return Utilities.sorted(result);
}
/**
* @return a set of the resource and type names defined for this version
*/
public Set<String> getTypeNameSet() {
Set<String> result = new HashSet<String>();
for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION &&
VersionUtilities.versionsCompatible(context.getVersion(), sd.getFhirVersion().toCode())) {
result.add(sd.getName());
}
}
return result;
}
public String getLinkForUrl(String corePath, String url) {
if (url == null) {
return null;
}
if (context.hasResource(Resource.class, url)) {
Resource cr = context.fetchResource(Resource.class, url);
return cr.getUserString("path");
}
return null;
}
protected String tail(String url) {
if (Utilities.noString(url)) {
return "noname";
}
if (url.contains("/")) {
return url.substring(url.lastIndexOf("/")+1);
}
return url;
}
private boolean hasUrlProperty(StructureDefinition sd) {
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(sd.getType()+".url")) {
return true;
}
}
return false;
}
// -- profile services ---------------------------------------------------------
/**
* @return a list of the resource names that are canonical resources defined for this version
*/
public List<String> getCanonicalResourceNames() {
if (canonicalResourceNames == null) {
canonicalResourceNames = new ArrayList<>();
Set<String> names = new HashSet<>();
for (StructureDefinition sd : allStructures()) {
if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && hasUrlProperty(sd)) {
names.add(sd.getType());
}
}
canonicalResourceNames.addAll(Utilities.sorted(names));
}
return canonicalResourceNames;
}
/**
* @return a list of all structure definitions, with snapshots generated (if possible)
*/
public List<StructureDefinition> allStructures(){
if (allStructuresList.isEmpty()) {
Set<StructureDefinition> set = new HashSet<StructureDefinition>();
for (StructureDefinition sd : getStructures()) {
if (!set.contains(sd)) {
try {
generateSnapshot(sd);
// new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
} catch (Exception e) {
if (!isSuppressDebugMessages()) {
System.out.println("Unable to generate snapshot @2 for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
if (context.getLogger() != null) {
e.printStackTrace();
}
}
}
allStructuresList.add(sd);
set.add(sd);
}
}
}
return allStructuresList;
}
/**
* @return a list of all structure definitions, without trying to generate snapshots
*/
public List<StructureDefinition> getStructures() {
return context.fetchResourcesByType(StructureDefinition.class);
}
/**
* Given a structure definition, generate a snapshot (or regenerate it)
* @param p
* @throws DefinitionException
* @throws FHIRException
*/
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p))) {
if (!p.hasBaseDefinition())
throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getBaseDefinition(), p);
if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
throw new Error("Not done yet"); // sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
}
if (sd == null) {
throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
}
List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
List<String> errors = new ArrayList<String>();
ProfileUtilities pu = new ProfileUtilities(context, msgs, this);
pu.setThrowException(false);
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
pu.sortDifferential(sd, p, p.getUrl(), errors);
}
pu.setDebug(false);
for (String err : errors) {
msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
}
pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName());
for (ValidationMessage msg : msgs) {
if ((msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) {
if (!msg.isIgnorableError()) {
throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
} else {
System.err.println(msg.getMessage());
}
}
}
if (!p.hasSnapshot())
throw new FHIRException(context.formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
pu = null;
}
}
// work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind.
private boolean isProfileNeedsRegenerate(StructureDefinition p) {
boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse");
if (needs) {
p.setUserData("hack.regnerated", "yes");
}
return needs;
}
@Override
public boolean isPrimitiveType(String type) {
return context.isPrimitiveType(type);
}
@Override
public boolean isDatatype(String type) {
StructureDefinition sd = context.fetchTypeDefinition(type);
return sd != null && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
}
@Override
public boolean isResource(String t) {
if (getConcreteResourceSet().contains(t)) {
return true;
}
StructureDefinition sd;
try {
sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
} catch (Exception e) {
return false;
}
if (sd == null)
return false;
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
return false;
return sd.getKind() == StructureDefinitionKind.RESOURCE;
}
@Override
public boolean hasLinkFor(String typeSimple) {
return false;
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
return null;
}
@Override
public String getLinkForProfile(StructureDefinition profile, String url) {
return null;
}
@Override
public boolean prependLinks() {
return false;
}
public StructureDefinition fetchByJsonName(String key) {
for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
if (ed != null) {
return sd;
}
}
return null;
}
public Set<String> getConcreteResourceSet() {
if (concreteResourceNameSet == null) {
concreteResourceNameSet = new HashSet<>();
for (StructureDefinition sd : getStructures()) {
if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
concreteResourceNameSet.add(sd.getType());
}
}
}
return concreteResourceNameSet;
}
public List<String> getConcreteResources() {
if (concreteResourceNames == null) {
concreteResourceNames = new ArrayList<>();
concreteResourceNames.addAll(Utilities.sorted(getConcreteResourceSet()));
}
return concreteResourceNames;
}
public List<StructureMap> listMaps(String url) {
List<StructureMap> res = new ArrayList<>();
String start = url.substring(0, url.indexOf("*"));
String end = url.substring(url.indexOf("*")+1);
for (StructureMap map : context.fetchResourcesByType(StructureMap.class)) {
String u = map.getUrl();
if (u.startsWith(start) && u.endsWith(end)) {
res.add(map);
}
}
return res;
}
public List<String> fetchCodeSystemVersions(String system) {
List<String> res = new ArrayList<>();
for (CodeSystem cs : context.fetchResourcesByType(CodeSystem.class)) {
if (system.equals(cs.getUrl()) && cs.hasVersion()) {
res.add(cs.getVersion());
}
}
return res;
}
public StructureDefinition findType(String typeName) {
StructureDefinition t = context.fetchTypeDefinition(typeName);
if (t != null) {
return t;
}
List<StructureDefinition> candidates = new ArrayList<>();
for (StructureDefinition sd : getStructures()) {
if (sd.getType().equals(typeName)) {
candidates.add(sd);
}
}
if (candidates.size() == 1) {
return candidates.get(0);
}
return null;
}
public StructureDefinition fetchProfileByIdentifier(String tid) {
for (StructureDefinition sd : context.fetchResourcesByType(StructureDefinition.class)) {
for (Identifier ii : sd.getIdentifier()) {
if (tid.equals(ii.getValue())) {
return sd;
}
}
}
return null;
}
public boolean isAbstractType(String typeName) {
StructureDefinition sd = context.fetchTypeDefinition(typeName);
if (sd != null) {
return sd.getAbstract();
}
return false;
}
public boolean isDomainResource(String typeName) {
StructureDefinition sd = context.fetchTypeDefinition(typeName);
while (sd != null) {
if ("DomainResource".equals(sd.getType())) {
return true;
}
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
}
return false;
}
public IWorkerContext getWorker() {
return context;
}
}

View File

@ -169,8 +169,10 @@ public interface IWorkerContext {
*/
public <T extends Resource> T fetchResource(Class<T> class_, String uri);
public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version);
public <T extends Resource> T fetchResource(Class<T> class_, String uri, Resource source);
public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException;
public <T extends Resource> List<T> fetchResourcesByType(Class<T> class_);
/**
* Variation of fetchResource when you have a string type, and don't need the
@ -485,6 +487,7 @@ public interface IWorkerContext {
public void setOverrideVersionNs(String value);
public StructureDefinition fetchTypeDefinition(String typeName);
public List<StructureDefinition> fetchTypeDefinitions(String n);
public void setUcumService(UcumService ucumService);

View File

@ -1,6 +1,10 @@
package org.hl7.fhir.r4.fhirpath;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.SourceLocation;
import org.hl7.fhir.utilities.Utilities;
@ -11,20 +15,23 @@ import org.hl7.fhir.utilities.Utilities;
public class FHIRLexer {
public class FHIRLexerException extends FHIRException {
public FHIRLexerException() {
super();
private SourceLocation location;
public FHIRLexerException(String message) {
super(message);
}
public FHIRLexerException(String message, Throwable cause) {
super(message, cause);
}
public FHIRLexerException(String message) {
public FHIRLexerException(String message, SourceLocation location) {
super(message);
this.location = location;
}
public FHIRLexerException(Throwable cause) {
super(cause);
public SourceLocation getLocation() {
return location;
}
}
@ -33,25 +40,44 @@ public class FHIRLexer {
private int cursor;
private int currentStart;
private String current;
private List<String> comments = new ArrayList<>();
private SourceLocation currentLocation;
private SourceLocation currentStartLocation;
private int id;
private String name;
private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host
private SourceLocation commentLocation;
private boolean metadataFormat;
private boolean allowDoubleQuotes;
public FHIRLexer(String source, String name) throws FHIRLexerException {
this.source = source;
this.source = source == null ? "" : Utilities.stripBOM(source);
this.name = name == null ? "??" : name;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, int i) throws FHIRLexerException {
this.source = source;
this.source = Utilities.stripBOM(source);
this.cursor = i;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, int i, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = Utilities.stripBOM(source);
this.cursor = i;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, String name, boolean metadataFormat, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source == null ? "" : Utilities.stripBOM(source);
this.name = name == null ? "??" : name;
this.metadataFormat = metadataFormat;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
public String getCurrent() {
return current;
}
@ -61,18 +87,15 @@ public class FHIRLexer {
}
public boolean isConstant() {
return current != null && (current.charAt(0) == '\'' || current.charAt(0) == '"') || current.charAt(0) == '@'
|| current.charAt(0) == '%' || current.charAt(0) == '-' || current.charAt(0) == '+'
|| (current.charAt(0) >= '0' && current.charAt(0) <= '9') || current.equals("true") || current.equals("false")
|| current.equals("{}");
return FHIRPathConstant.isFHIRPathConstant(current);
}
public boolean isFixedName() {
return current != null && (current.charAt(0) == '`');
return FHIRPathConstant.isFHIRPathFixedName(current);
}
public boolean isStringConstant() {
return current.charAt(0) == '\'' || current.charAt(0) == '"' || current.charAt(0) == '`';
return FHIRPathConstant.isFHIRPathStringConstant(current);
}
public String take() throws FHIRLexerException {
@ -84,7 +107,7 @@ public class FHIRLexer {
public int takeInt() throws FHIRLexerException {
String s = current;
if (!Utilities.isInteger(s))
throw error("Found " + current + " expecting an integer");
throw error("Found "+current+" expecting an integer");
next();
return Integer.parseInt(s);
}
@ -99,12 +122,10 @@ public class FHIRLexer {
if (current.equals("*") || current.equals("**"))
return true;
if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z')
|| (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) {
for (int i = 1; i < current.length(); i++)
if (!((current.charAt(1) >= 'A' && current.charAt(1) <= 'Z')
|| (current.charAt(1) >= 'a' && current.charAt(1) <= 'z')
|| (current.charAt(1) >= '0' && current.charAt(1) <= '9')))
if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) {
for (int i = 1; i < current.length(); i++)
if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') ||
(current.charAt(1) >= '0' && current.charAt(1) <= '9')))
return false;
return true;
}
@ -112,11 +133,11 @@ public class FHIRLexer {
}
public FHIRLexerException error(String msg) {
return error(msg, currentLocation.toString());
return error(msg, currentLocation.toString(), currentLocation);
}
public FHIRLexerException error(String msg, String location) {
return new FHIRLexerException("Error in " + name + " at " + location + ": " + msg);
public FHIRLexerException error(String msg, String location, SourceLocation loc) {
return new FHIRLexerException("Error @"+location+": "+msg, loc);
}
public void next() throws FHIRLexerException {
@ -126,34 +147,30 @@ public class FHIRLexer {
currentStartLocation = currentLocation;
if (cursor < source.length()) {
char ch = source.charAt(cursor);
if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') {
if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') {
cursor++;
if (cursor < source.length()
&& (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-')
|| (ch == '-' && source.charAt(cursor) == '>'))
if (cursor < source.length() && (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-') || (ch == '-' && source.charAt(cursor) == '>'))
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '.') {
} else if (ch == '.' ) {
cursor++;
if (cursor < source.length() && (source.charAt(cursor) == '.'))
if (cursor < source.length() && (source.charAt(cursor) == '.'))
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch >= '0' && ch <= '9') {
cursor++;
cursor++;
boolean dotted = false;
while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9')
|| (source.charAt(cursor) == '.') && !dotted)) {
while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || (source.charAt(cursor) == '.') && !dotted)) {
if (source.charAt(cursor) == '.')
dotted = true;
cursor++;
}
if (source.charAt(cursor - 1) == '.')
if (source.charAt(cursor-1) == '.')
cursor--;
current = source.substring(currentStart, cursor);
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z')
|| (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')
|| (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_'))
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_'))
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '%') {
@ -164,19 +181,20 @@ public class FHIRLexer {
cursor++;
cursor++;
} else
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z')
|| (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')
|| (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':'
|| source.charAt(cursor) == '-'))
cursor++;
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-' || source.charAt(cursor) == '_'))
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '/') {
cursor++;
if (cursor < source.length() && (source.charAt(cursor) == '/')) {
// this is en error - should already have been skipped
error("This shoudn't happen?");
// we've run into metadata
cursor++;
cursor++;
current = source.substring(currentStart, cursor);
} else {
current = source.substring(currentStart, cursor);
}
current = source.substring(currentStart, cursor);
} else if (ch == '$') {
cursor++;
while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z'))
@ -188,42 +206,42 @@ public class FHIRLexer {
if (ch == '}')
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '"') {
} else if (ch == '"' && allowDoubleQuotes) {
cursor++;
boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) {
if (escape)
escape = false;
else
else
escape = (source.charAt(cursor) == '\\');
cursor++;
}
if (cursor == source.length())
throw error("Unterminated string");
cursor++;
current = "\"" + source.substring(currentStart + 1, cursor - 1) + "\"";
current = "\""+source.substring(currentStart+1, cursor-1)+"\"";
} else if (ch == '`') {
cursor++;
boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) {
if (escape)
escape = false;
else
else
escape = (source.charAt(cursor) == '\\');
cursor++;
}
if (cursor == source.length())
throw error("Unterminated string");
cursor++;
current = "`" + source.substring(currentStart + 1, cursor - 1) + "`";
} else if (ch == '\'') {
current = "`"+source.substring(currentStart+1, cursor-1)+"`";
} else if (ch == '\''){
cursor++;
char ech = ch;
boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != ech)) {
if (escape)
escape = false;
else
else
escape = (source.charAt(cursor) == '\\');
cursor++;
}
@ -232,26 +250,32 @@ public class FHIRLexer {
cursor++;
current = source.substring(currentStart, cursor);
if (ech == '\'')
current = "\'" + current.substring(1, current.length() - 1) + "\'";
current = "\'"+current.substring(1, current.length() - 1)+"\'";
} else if (ch == '`') {
cursor++;
boolean escape = false;
while (cursor < source.length() && (escape || source.charAt(cursor) != '`')) {
if (escape)
escape = false;
else
else
escape = (source.charAt(cursor) == '\\');
cursor++;
}
if (cursor == source.length())
throw error("Unterminated string");
cursor++;
current = "`" + source.substring(currentStart + 1, cursor - 1) + "`";
} else if (ch == '@') {
current = "`"+source.substring(currentStart+1, cursor-1)+"`";
} else if (ch == '|' && liquidMode) {
cursor++;
ch = source.charAt(cursor);
if (ch == '|')
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '@'){
int start = cursor;
cursor++;
while (cursor < source.length() && isDateChar(source.charAt(cursor), start))
cursor++;
cursor++;
current = source.substring(currentStart, cursor);
} else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then
cursor++;
@ -261,23 +285,36 @@ public class FHIRLexer {
}
private void skipWhitespaceAndComments() {
comments.clear();
commentLocation = null;
boolean last13 = false;
boolean done = false;
while (cursor < source.length() && !done) {
if (cursor < source.length() - 1 && "//".equals(source.substring(cursor, cursor + 2))) {
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n'))
cursor++;
} else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor + 2))) {
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor + 2))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2)) && !isMetadataStart()) {
if (commentLocation == null) {
commentLocation = currentLocation.copy();
}
if (cursor >= source.length() - 1) {
int start = cursor+2;
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) {
cursor++;
}
comments.add(source.substring(start, cursor).trim());
} else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor+2))) {
if (commentLocation == null) {
commentLocation = currentLocation.copy();
}
int start = cursor+2;
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
}
if (cursor >= source.length() -1) {
error("Unfinished comment");
} else {
comments.add(source.substring(start, cursor).trim());
cursor = cursor + 2;
}
} else if (Character.isWhitespace(source.charAt(cursor))) {
} else if (Utilities.isWhitespace(source.charAt(cursor))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
} else {
@ -285,13 +322,15 @@ public class FHIRLexer {
}
}
}
private boolean isDateChar(char ch, int start) {
int eot = source.charAt(start + 1) == 'T' ? 10 : 20;
return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch)
|| (cursor - start == eot && ch == '.' && cursor < source.length() - 1
&& Character.isDigit(source.charAt(cursor + 1)));
private boolean isMetadataStart() {
return metadataFormat && cursor < source.length() - 2 && "///".equals(source.substring(cursor, cursor+3));
}
private boolean isDateChar(char ch,int start) {
int eot = source.charAt(start+1) == 'T' ? 10 : 20;
return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch) || (cursor-start == eot && ch == '.' && cursor < source.length()-1&& Character.isDigit(source.charAt(cursor+1)));
}
public boolean isOp() {
@ -320,35 +359,60 @@ public class FHIRLexer {
return !done() && current.startsWith("//");
}
public boolean hasComments() {
return comments.size() > 0;
}
public List<String> getComments() {
return comments;
}
public String getAllComments() {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("\r\n");
b.addAll(comments);
comments.clear();
return b.toString();
}
public String getFirstComment() {
if (hasComments()) {
String s = comments.get(0);
comments.remove(0);
return s;
} else {
return null;
}
}
public boolean hasToken(String kw) {
return !done() && kw.equals(current);
}
public boolean hasToken(String... names) {
if (done())
if (done())
return false;
for (String s : names)
if (s.equals(current))
return true;
return false;
}
public void token(String kw) throws FHIRLexerException {
if (!kw.equals(current))
throw error("Found \"" + current + "\" expecting \"" + kw + "\"");
if (!kw.equals(current))
throw error("Found \""+current+"\" expecting \""+kw+"\"");
next();
}
public String readConstant(String desc) throws FHIRLexerException {
if (!isStringConstant())
throw error("Found " + current + " expecting \"[" + desc + "]\"");
throw error("Found "+current+" expecting \"["+desc+"]\"");
return processConstant(take());
}
public String readFixedName(String desc) throws FHIRLexerException {
if (!isFixedName())
throw error("Found " + current + " expecting \"[" + desc + "]\"");
throw error("Found "+current+" expecting \"["+desc+"]\"");
return processFixedName(take());
}
@ -356,21 +420,21 @@ public class FHIRLexer {
public String processConstant(String s) throws FHIRLexerException {
StringBuilder b = new StringBuilder();
int i = 1;
while (i < s.length() - 1) {
while (i < s.length()-1) {
char ch = s.charAt(i);
if (ch == '\\') {
i++;
switch (s.charAt(i)) {
case 't':
case 't':
b.append('\t');
break;
case 'r':
b.append('\r');
break;
case 'n':
case 'n':
b.append('\n');
break;
case 'f':
case 'f':
b.append('\f');
break;
case '\'':
@ -382,20 +446,20 @@ public class FHIRLexer {
case '`':
b.append('`');
break;
case '\\':
case '\\':
b.append('\\');
break;
case '/':
case '/':
b.append('/');
break;
case 'u':
i++;
int uc = Integer.parseInt(s.substring(i, i + 4), 16);
int uc = Integer.parseInt(s.substring(i, i+4), 16);
b.append(Character.toString(uc));
i = i + 4;
break;
default:
throw new FHIRLexerException("Unknown character escape \\" + s.charAt(i));
throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation);
}
} else {
b.append(ch);
@ -404,25 +468,25 @@ public class FHIRLexer {
}
return b.toString();
}
public String processFixedName(String s) throws FHIRLexerException {
StringBuilder b = new StringBuilder();
int i = 1;
while (i < s.length() - 1) {
while (i < s.length()-1) {
char ch = s.charAt(i);
if (ch == '\\') {
i++;
switch (s.charAt(i)) {
case 't':
case 't':
b.append('\t');
break;
case 'r':
b.append('\r');
break;
case 'n':
case 'n':
b.append('\n');
break;
case 'f':
case 'f':
b.append('\f');
break;
case '\'':
@ -431,20 +495,20 @@ public class FHIRLexer {
case '"':
b.append('"');
break;
case '\\':
case '\\':
b.append('\\');
break;
case '/':
case '/':
b.append('/');
break;
case 'u':
i++;
int uc = Integer.parseInt(s.substring(i, i + 4), 16);
int uc = Integer.parseInt(s.substring(i, i+4), 32);
b.append(Character.toString(uc));
i = i + 4;
break;
default:
throw new FHIRLexerException("Unknown character escape \\" + s.charAt(i));
throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation);
}
} else {
b.append(ch);
@ -457,9 +521,9 @@ public class FHIRLexer {
public void skipToken(String token) throws FHIRLexerException {
if (getCurrent().equals(token))
next();
}
public String takeDottedToken() throws FHIRLexerException {
StringBuilder b = new StringBuilder();
b.append(take());
@ -478,5 +542,39 @@ public class FHIRLexer {
public int getCurrentStart() {
return currentStart;
}
public String getSource() {
return source;
}
public boolean isLiquidMode() {
return liquidMode;
}
public void setLiquidMode(boolean liquidMode) {
this.liquidMode = liquidMode;
}
public SourceLocation getCommentLocation() {
return this.commentLocation;
}
public boolean isMetadataFormat() {
return metadataFormat;
}
public void setMetadataFormat(boolean metadataFormat) {
this.metadataFormat = metadataFormat;
}
public List<String> cloneComments() {
List<String> res = new ArrayList<>();
res.addAll(getComments());
return res;
}
public String tokenWithTrailingComment(String token) {
int line = getCurrentLocation().getLine();
token(token);
if (getComments().size() > 0 && getCommentLocation().getLine() == line) {
return getFirstComment();
} else {
return null;
}
}
public boolean isAllowDoubleQuotes() {
return allowDoubleQuotes;
}
}

View File

@ -32,6 +32,8 @@ package org.hl7.fhir.r4.fhirpath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -42,32 +44,49 @@ import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
public class TypeDetails {
public class ProfiledTypeSorter implements Comparator<ProfiledType> {
@Override
public int compare(ProfiledType o1, ProfiledType o2) {
return o1.uri.compareTo(o2.uri);
}
}
public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/";
public static final String FP_NS = "http://hl7.org/fhirpath/";
public static final String FP_String = "http://hl7.org/fhirpath/String";
public static final String FP_Boolean = "http://hl7.org/fhirpath/Boolean";
public static final String FP_Integer = "http://hl7.org/fhirpath/Integer";
public static final String FP_Decimal = "http://hl7.org/fhirpath/Decimal";
public static final String FP_Quantity = "http://hl7.org/fhirpath/Quantity";
public static final String FP_DateTime = "http://hl7.org/fhirpath/DateTime";
public static final String FP_Time = "http://hl7.org/fhirpath/Time";
public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/SimpleTypeInfo";
public static final String FP_ClassInfo = "http://hl7.org/fhirpath/ClassInfo";
public static final String FP_String = "http://hl7.org/fhirpath/System.String";
public static final String FP_Boolean = "http://hl7.org/fhirpath/System.Boolean";
public static final String FP_Integer = "http://hl7.org/fhirpath/System.Integer";
public static final String FP_Decimal = "http://hl7.org/fhirpath/System.Decimal";
public static final String FP_Quantity = "http://hl7.org/fhirpath/System.Quantity";
public static final String FP_DateTime = "http://hl7.org/fhirpath/System.DateTime";
public static final String FP_Time = "http://hl7.org/fhirpath/System.Time";
public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/System.SimpleTypeInfo";
public static final String FP_ClassInfo = "http://hl7.org/fhirpath/System.ClassInfo";
public static final Set<String> FP_NUMBERS = new HashSet<String>(Arrays.asList(FP_Integer, FP_Decimal));
public static class ProfiledType {
@Override
public String toString() {
return uri;
}
private String uri;
private List<String> profiles; // or, not and
private List<ElementDefinitionBindingComponent> bindings;
public ProfiledType(String n) {
uri = ns(n);
uri = ns(n);
}
public String getUri() {
return uri;
}
@ -75,7 +94,6 @@ public class TypeDetails {
public boolean hasProfiles() {
return profiles != null && profiles.size() > 0;
}
public List<String> getProfiles() {
return profiles;
}
@ -83,13 +101,12 @@ public class TypeDetails {
public boolean hasBindings() {
return bindings != null && bindings.size() > 0;
}
public List<ElementDefinitionBindingComponent> getBindings() {
return bindings;
}
public static String ns(String n) {
return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS + n;
return Utilities.isAbsoluteUrl(n) ? n : FHIR_NS+n;
}
public void addProfile(String profile) {
@ -113,14 +130,26 @@ public class TypeDetails {
for (UriType u : list)
profiles.add(u.getValue());
}
public boolean isSystemType() {
return uri.startsWith(FP_NS);
}
}
public String describeMin() {
if (uri.startsWith(FP_NS)) {
return "System."+uri.substring(FP_NS.length());
}
if (uri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
return "FHIR."+uri.substring("http://hl7.org/fhir/StructureDefinition/".length());
}
return uri;
}
}
private List<ProfiledType> types = new ArrayList<ProfiledType>();
private CollectionStatus collectionStatus;
private Set<String> targets; // or, not and, canonical urls
private boolean choice;
public TypeDetails(CollectionStatus collectionStatus, String... names) {
super();
@ -129,7 +158,6 @@ public class TypeDetails {
this.types.add(new ProfiledType(n));
}
}
public TypeDetails(CollectionStatus collectionStatus, Set<String> names) {
super();
this.collectionStatus = collectionStatus;
@ -137,20 +165,21 @@ public class TypeDetails {
addType(new ProfiledType(n));
}
}
public TypeDetails(CollectionStatus collectionStatus, ProfiledType pt) {
super();
this.collectionStatus = collectionStatus;
this.types.add(pt);
}
private TypeDetails() {
}
public String addType(String n) {
ProfiledType pt = new ProfiledType(n);
String res = pt.uri;
addType(pt);
return res;
}
public String addType(String n, String p) {
ProfiledType pt = new ProfiledType(n);
pt.addProfile(p);
@ -158,7 +187,7 @@ public class TypeDetails {
addType(pt);
return res;
}
public void addType(ProfiledType pt) {
for (ProfiledType et : types) {
if (et.uri.equals(pt.uri)) {
@ -181,99 +210,163 @@ public class TypeDetails {
return;
}
}
types.add(pt);
types.add(pt);
}
public void addType(CollectionStatus status, ProfiledType pt) {
addType(pt);
if (collectionStatus == null) {
collectionStatus = status;
} else {
switch (status) {
case ORDERED:
if (collectionStatus == CollectionStatus.SINGLETON) {
collectionStatus = status;
}
break;
case SINGLETON:
break;
case UNORDERED:
collectionStatus = status;
break;
default:
break;
}
}
}
public void addTypes(Collection<String> names) {
for (String n : names)
for (String n : names)
addType(new ProfiledType(n));
}
public boolean hasType(IWorkerContext context, String... tn) {
for (String n : tn) {
for (String n: tn) {
String t = ProfiledType.ns(n);
if (typesContains(t))
return true;
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time",
"ClassInfo", "SimpleTypeInfo")) {
t = FP_NS + Utilities.capitalize(n);
if (typesContains(t))
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
t = FP_NS+"System."+Utilities.capitalize(n);
if (typesContains(t)) {
return true;
}
}
t = ProfiledType.ns(n);
StructureDefinition sd = context.fetchTypeDefinition(t);
if (sd != null && sd.getKind() != StructureDefinitionKind.LOGICAL && Utilities.existsInList(sd.getType(), "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time")) {
t = FP_NS+"System."+Utilities.capitalize(sd.getType());
if (typesContains(t)) {
return true;
}
}
}
for (String n : tn) {
for (String n: tn) {
String id = n.contains("#") ? n.substring(0, n.indexOf("#")) : n;
String tail = null;
if (n.contains("#")) {
tail = n.substring(n.indexOf("#") + 1);
tail = n.substring( n.indexOf("#")+1);
tail = tail.substring(tail.indexOf("."));
}
String t = ProfiledType.ns(n);
StructureDefinition sd = context.fetchResource(StructureDefinition.class, t);
while (sd != null) {
if (tail == null && typesContains(sd.getUrl()))
return true;
if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl())))
return true;
if (tail != null && typesContains(sd.getUrl() + "#" + sd.getType() + tail))
return true;
if (sd.hasBaseDefinition()) {
if (sd.getType().equals("uri"))
sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string");
else
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
} else
sd = null;
List<StructureDefinition> list = new ArrayList<>();
if (!Utilities.isAbsoluteUrl(n)) {
list.addAll(context.fetchTypeDefinitions(n));
} else {
String t = ProfiledType.ns(n);
StructureDefinition sd = context.fetchResource(StructureDefinition.class, t);
if (sd != null) {
list.add(sd);
}
}
for (int i = 0; i < list.size(); i++) {
StructureDefinition sd = list.get(i);
while (sd != null) {
if (tail == null && typesContains(sd.getUrl()))
return true;
if (tail == null && getSystemType(sd.getUrl()) != null && typesContains(getSystemType(sd.getUrl())))
return true;
if (tail != null && typesContains(sd.getUrl()+"#"+sd.getType()+tail))
return true;
if ("http://hl7.org/fhir/StructureDefinition/string".equals(sd.getUrl()) && typesContains(FP_String)) {
return true; // this is work around for R3
}
if (sd.hasBaseDefinition()) {
if (sd.getType().equals("uri"))
sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/string");
else
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
} else {
sd = null;
}
}
}
}
return false;
}
private String getSystemType(String url) {
if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String code = url.substring(40);
if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity"))
return FP_NS + Utilities.capitalize(code);
if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity"))
return FP_NS+"System.."+Utilities.capitalize(code);
}
return null;
}
private boolean typesContains(String t) {
for (ProfiledType pt : types)
if (pt.uri.equals(t))
return true;
return false;
}
public void update(TypeDetails source) {
for (ProfiledType pt : source.types)
addType(pt);
if (collectionStatus == null)
if (collectionStatus == null || collectionStatus == CollectionStatus.SINGLETON)
collectionStatus = source.collectionStatus;
else if (source.collectionStatus == CollectionStatus.UNORDERED)
collectionStatus = source.collectionStatus;
else
collectionStatus = CollectionStatus.ORDERED;
if (source.targets != null) {
if (targets == null) {
targets = new HashSet<>();
}
targets.addAll(source.targets);
}
if (source.isChoice()) {
choice = true;
}
}
public TypeDetails union(TypeDetails right) {
TypeDetails result = new TypeDetails(null);
if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
result.collectionStatus = CollectionStatus.UNORDERED;
else
else
result.collectionStatus = CollectionStatus.ORDERED;
for (ProfiledType pt : types)
result.addType(pt);
for (ProfiledType pt : right.types)
result.addType(pt);
if (targets != null || right.targets != null) {
result.targets = new HashSet<>();
if (targets != null) {
result.targets.addAll(targets);
}
if (right.targets != null) {
result.targets.addAll(right.targets);
}
}
return result;
}
public TypeDetails intersect(TypeDetails right) {
TypeDetails result = new TypeDetails(null);
if (right.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED)
result.collectionStatus = CollectionStatus.UNORDERED;
else
else
result.collectionStatus = CollectionStatus.ORDERED;
for (ProfiledType pt : types) {
boolean found = false;
@ -284,77 +377,106 @@ public class TypeDetails {
}
for (ProfiledType pt : right.types)
result.addType(pt);
if (targets != null && right.targets != null) {
result.targets = new HashSet<>();
for (String s : targets) {
if (right.targets.contains(s)) {
result.targets.add(s);
}
}
}
return result;
}
public boolean hasNoTypes() {
return types.isEmpty();
}
public Set<String> getTypes() {
Set<String> res = new HashSet<String>();
for (ProfiledType pt : types)
res.add(pt.uri);
return res;
}
public TypeDetails toSingleton() {
TypeDetails result = new TypeDetails(CollectionStatus.SINGLETON);
result.types.addAll(types);
return result;
}
public TypeDetails toOrdered() {
TypeDetails result = new TypeDetails(CollectionStatus.ORDERED);
result.types.addAll(types);
return result;
}
public TypeDetails toUnordered() {
TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
result.types.addAll(types);
return result;
}
public CollectionStatus getCollectionStatus() {
return collectionStatus;
}
private boolean hasType(ProfiledType pt) {
return hasType(pt.uri);
}
public boolean hasType(String n) {
String t = ProfiledType.ns(n);
if (typesContains(t))
return true;
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time",
"ClassInfo", "SimpleTypeInfo")) {
t = FP_NS + Utilities.capitalize(n);
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
t = FP_NS+"System."+Utilities.capitalize(n);
if (typesContains(t))
return true;
}
return false;
}
public boolean hasType(Set<String> tn) {
for (String n : tn) {
for (String n: tn) {
String t = ProfiledType.ns(n);
if (typesContains(t))
return true;
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time",
"ClassInfo", "SimpleTypeInfo")) {
t = FP_NS + Utilities.capitalize(n);
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
t = FP_NS+"System."+Utilities.capitalize(n);
if (typesContains(t))
return true;
}
}
return false;
}
public String describe() {
return getTypes().toString();
return Utilities.sorted(getTypes()).toString();
}
public String describeMin() {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ProfiledType pt : sortedTypes(types))
b.append(pt.describeMin());
return b.toString();
}
private List<ProfiledType> sortedTypes(List<ProfiledType> types2) {
List<ProfiledType> list = new ArrayList<>();
Collections.sort(list, new ProfiledTypeSorter());
return list;
}
public String getType() {
for (ProfiledType pt : types)
return pt.uri;
return null;
}
@Override
public String toString() {
return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString())
+ getTypes().toString();
return (collectionStatus == null ? collectionStatus.SINGLETON.toString() : collectionStatus.toString()) + getTypes().toString();
}
public String getTypeCode() throws DefinitionException {
if (types.size() != 1)
throw new DefinitionException("Multiple types? (" + types.toString() + ")");
throw new DefinitionException("Multiple types? ("+types.toString()+")");
for (ProfiledType pt : types)
if (pt.uri.startsWith("http://hl7.org/fhir/StructureDefinition/"))
return pt.uri.substring(40);
@ -362,11 +484,9 @@ public class TypeDetails {
return pt.uri;
return null;
}
public List<ProfiledType> getProfiledTypes() {
return types;
}
public boolean hasBinding() {
for (ProfiledType pt : types) {
if (pt.hasBindings())
@ -374,7 +494,6 @@ public class TypeDetails {
}
return false;
}
public ElementDefinitionBindingComponent getBinding() {
for (ProfiledType pt : types) {
for (ElementDefinitionBindingComponent b : pt.getBindings())
@ -382,5 +501,97 @@ public class TypeDetails {
}
return null;
}
public void addTarget(String url) {
if (targets == null) {
targets = new HashSet<>();
}
targets.add(url);
}
public Set<String> getTargets() {
return targets;
}
public boolean typesHaveTargets() {
for (ProfiledType pt : types) {
if (Utilities.existsInList(pt.getUri(), "Reference", "CodeableReference", "canonical", "http://hl7.org/fhir/StructureDefinition/Reference", "http://hl7.org/fhir/StructureDefinition/CodeableReference", "http://hl7.org/fhir/StructureDefinition/canonical")) {
return true;
}
}
return false;
}
public void addTargets(Set<String> src) {
if (src != null) {
for (String s : src) {
addTarget(s);
}
}
}
public TypeDetails copy() {
TypeDetails td = new TypeDetails();
td.types.addAll(types);
td.collectionStatus = collectionStatus;
if (targets != null ) {
td.targets = new HashSet<>();
td.targets.addAll(targets);
}
return td;
}
public boolean matches(TypeDetails other) {
boolean result = collectionStatus == other.collectionStatus && types.equals(other.types);
if (targets == null) {
return result && other.targets == null;
} else {
return result && targets.equals(other.targets);
}
}
public void addTypes(TypeDetails other) {
if (other.collectionStatus != CollectionStatus.SINGLETON) {
if (other.collectionStatus == CollectionStatus.UNORDERED || collectionStatus == CollectionStatus.UNORDERED) {
collectionStatus = CollectionStatus.UNORDERED;
} else {
collectionStatus = CollectionStatus.ORDERED;
}
}
for (ProfiledType pt : other.types) {
addType(pt);
}
if (other.targets != null) {
if (targets == null) {
targets = new HashSet<>();
}
targets.addAll(other.targets);
}
}
public boolean contains(TypeDetails other) {
// TODO Auto-generated method stub
if (other.collectionStatus != collectionStatus) {
return false;
}
for (ProfiledType pt : other.types) {
if (!hasType(pt)) {
return false;
}
}
return true;
}
public static TypeDetails empty() {
return new TypeDetails(CollectionStatus.SINGLETON);
}
public boolean isList() {
return collectionStatus != null && collectionStatus.isList();
}
// for SQL-on-FHIR: warnings when .ofType() is not paired with a choice element
public void setChoice(boolean b) {
choice = true;
}
public boolean isChoice() {
return choice;
}
}

View File

@ -212,6 +212,17 @@ public abstract class Base implements Serializable, IBase, IElement {
return result;
}
public Base getChildValueByName(String name) {
Property p = getChildByName(name);
if (p != null && p.hasValues()) {
if (p.getValues().size() > 1) {
throw new Error("Too manye values for "+name+" found");
} else {
return p.getValues().get(0);
}
}
return null;
}
public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException {
if (name.equals("*")) {
List<Property> children = new ArrayList<Property>();

View File

@ -39,5 +39,7 @@ public class Constants {
public final static String URI_REGEX = "((http|https)://([A-Za-z0-9\\\\\\.\\:\\%\\$]*\\/)*)?(Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceDefinition|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EffectEvidenceSynthesis|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|InsurancePlan|Invoice|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProduct|MedicinalProductAuthorization|MedicinalProductContraindication|MedicinalProductIndication|MedicinalProductIngredient|MedicinalProductInteraction|MedicinalProductManufactured|MedicinalProductPackaged|MedicinalProductPharmaceutical|MedicinalProductUndesirableEffect|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionOrder|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RelatedPerson|RequestGroup|ResearchDefinition|ResearchElementDefinition|ResearchStudy|ResearchSubject|RiskAssessment|RiskEvidenceSynthesis|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|Substance|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SubstanceSpecification|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestReport|TestScript|ValueSet|VerificationResult|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?";
public final static String LOCAL_REF_REGEX = "(Account|ActivityDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|CatalogEntry|ChargeItem|ChargeItemDefinition|Claim|ClaimResponse|ClinicalImpression|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceDefinition|DeviceMetric|DeviceRequest|DeviceUseStatement|DiagnosticReport|DocumentManifest|DocumentReference|EffectEvidenceSynthesis|Encounter|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|InsurancePlan|Invoice|Library|Linkage|List|Location|Measure|MeasureReport|Media|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProduct|MedicinalProductAuthorization|MedicinalProductContraindication|MedicinalProductIndication|MedicinalProductIngredient|MedicinalProductInteraction|MedicinalProductManufactured|MedicinalProductPackaged|MedicinalProductPharmaceutical|MedicinalProductUndesirableEffect|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionOrder|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|Patient|PaymentNotice|PaymentReconciliation|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RelatedPerson|RequestGroup|ResearchDefinition|ResearchElementDefinition|ResearchStudy|ResearchSubject|RiskAssessment|RiskEvidenceSynthesis|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|Substance|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SubstanceSpecification|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestReport|TestScript|ValueSet|VerificationResult|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}";
public final static String NS_SYSTEM_TYPE = "http://hl7.org/fhirpath/System.";
public static final String NS_FHIR_ROOT = "http://hl7.org/fhir";
public static final String NS_CDA_ROOT = "http://hl7.org/cda/stds/core";
}

View File

@ -4839,4 +4839,17 @@ public class StructureDefinition extends MetadataResource {
return hasVersion() ? getUrl()+"|"+getVersion() : getUrl();
}
public String getTypeName() {
String t = getType();
return StructureDefinitionKind.LOGICAL.equals(getKind()) && t.contains("/") ? t.substring(t.lastIndexOf("/")+1) : t;
}
public String getTypeTail() {
if (getType().contains("/")) {
return getType().substring(getType().lastIndexOf("/")+1);
} else {
return getType();
}
}
}

View File

@ -139,7 +139,7 @@ public class PECodeGenerator {
w(b, "public class "+name+" extends PEGeneratedBase {");
w(b);
if (url != null) {
w(b, " private static final String CANONICAL_URL = \""+url+"\";");
w(b, " public static final String CANONICAL_URL = \""+url+"\";");
w(b);
}
if (enums.length() > 0) {

View File

@ -444,4 +444,8 @@ public class LiquidEngine implements IEvaluationContext {
return engine.getWorker().fetchResource(ValueSet.class, url);
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}

View File

@ -252,6 +252,10 @@ public class StructureMapUtilities {
throw new Error("Not Implemented Yet");
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private IWorkerContext worker;
@ -751,7 +755,7 @@ public class StructureMapUtilities {
}
public StructureMap parse(String text, String srcName) throws FHIRException {
FHIRLexer lexer = new FHIRLexer(text, srcName);
FHIRLexer lexer = new FHIRLexer(text, srcName, true, true);
if (lexer.done())
throw lexer.error("Map Input cannot be empty");
lexer.skipComments();

View File

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

View File

@ -0,0 +1,102 @@
package org.hl7.fhir.r4.utils.sql;
import org.hl7.fhir.r4.utils.sql.Column;
import org.hl7.fhir.r4.utils.sql.ColumnKind;
public class Column {
private String name;
private int length;
private String type;
private ColumnKind kind;
private boolean isColl;
private boolean duplicateReported;
protected Column() {
super();
}
protected Column(String name, boolean isColl, String type, ColumnKind kind) {
super();
this.name = name;
this.isColl = isColl;
this.type = type;
this.kind = kind;
}
public String getName() {
return name;
}
public int getLength() {
return length;
}
public ColumnKind getKind() {
return kind;
}
public void setName(String name) {
this.name = name;
}
public void setLength(int length) {
this.length = length;
}
public void setKind(ColumnKind kind) {
this.kind = kind;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isColl() {
return isColl;
}
public void setColl(boolean isColl) {
this.isColl = isColl;
}
public String diff(Column other) {
if (!name.equals(other.name)) {
return "Names differ: '"+name+"' vs '"+other.name+"'";
}
if (kind != ColumnKind.Null && other.kind != ColumnKind.Null) {
if (length != other.length) {
return "Lengths differ: '"+length+"' vs '"+other.length+"'";
}
if (kind != other.kind) {
return "Kinds differ: '"+kind+"' vs '"+other.kind+"'";
}
if (isColl != other.isColl) {
return "Collection status differs: '"+isColl+"' vs '"+other.isColl+"'";
}
} else if (kind == ColumnKind.Null) {
kind = other.kind;
length = other.length;
isColl = other.isColl;
}
return null;
}
public boolean isDuplicateReported() {
return duplicateReported;
}
public void setDuplicateReported(boolean duplicateReported) {
this.duplicateReported = duplicateReported;
}
@Override
public String toString() {
return "Column [name=" + name + ", length=" + length + ", type=" + type + ", kind=" + kind + ", isColl=" + isColl
+ "]";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,717 @@
package org.hl7.fhir.r4.utils.sql;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.utils.sql.Validator.TrueFalseOrUnknown;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.fhirpath.ExpressionNode;
import org.hl7.fhir.r4.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r4.fhirpath.TypeDetails;
import org.hl7.fhir.r4.formats.JsonParser;
import org.hl7.fhir.r4.model.Base64BinaryType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.OidType;
import org.hl7.fhir.r4.model.PositiveIntType;
import org.hl7.fhir.r4.model.PrimitiveType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.TimeType;
import org.hl7.fhir.r4.model.UnsignedIntType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.UrlType;
import org.hl7.fhir.r4.model.UuidType;
import org.hl7.fhir.r4.utils.sql.Column;
import org.hl7.fhir.r4.utils.sql.ColumnKind;
import org.hl7.fhir.r4.fhirpath.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r4.fhirpath.FHIRPathEngine.IssueMessage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonBoolean;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonNumber;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.model.JsonProperty;
import org.hl7.fhir.utilities.json.model.JsonString;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public class Validator {
public enum TrueFalseOrUnknown {
TRUE, FALSE, UNKNOWN
}
private IWorkerContext context;
private FHIRPathEngine fpe;
private List<String> prohibitedNames = new ArrayList<String>();
private List<ValidationMessage> issues = new ArrayList<ValidationMessage>();
private TrueFalseOrUnknown supportsArrays;
private TrueFalseOrUnknown supportsComplexTypes;
private TrueFalseOrUnknown supportsNeedsName;
private String resourceName;
private String name;
public Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, @Nonnull TrueFalseOrUnknown supportsArrays, @Nonnull TrueFalseOrUnknown supportsComplexTypes, @Nonnull TrueFalseOrUnknown supportsNeedsName) {
super();
this.context = context;
this.fpe = fpe;
this.prohibitedNames = prohibitedNames;
this.supportsArrays = supportsArrays;
this.supportsComplexTypes = supportsComplexTypes;
this.supportsNeedsName = supportsNeedsName;
}
public String getResourceName() {
return resourceName;
}
public void checkViewDefinition(String path, JsonObject viewDefinition) {
checkProperties(viewDefinition, path, "resourceType", "url", "identifier", "name", "version", "title", "status", "experimental", "date", "publisher", "contact", "description", "useContext", "copyright", "resource", "constant", "select", "where");
JsonElement nameJ = viewDefinition.get("name");
if (nameJ == null) {
if (supportsNeedsName == null) {
hint(path, viewDefinition, "No name provided. A name is required in many contexts where a ViewDefinition is used");
} else if (supportsNeedsName == TrueFalseOrUnknown.TRUE) {
error(path, viewDefinition, "No name provided", IssueType.REQUIRED);
}
} else if (!(nameJ instanceof JsonString)) {
error(path, viewDefinition, "name must be a string", IssueType.INVALID);
} else {
name = nameJ.asString();
if (!isValidName(name)) {
error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT);
}
if (prohibitedNames.contains(name)) {
error(path, nameJ, "The name '"+name+"' on the viewDefinition is not allowed in this context", IssueType.BUSINESSRULE);
}
}
List<Column> columns = new ArrayList<>();
viewDefinition.setUserData("columns", columns);
JsonElement resourceNameJ = viewDefinition.get("resource");
if (resourceNameJ == null) {
error(path, viewDefinition, "No resource specified", IssueType.REQUIRED);
} else if (!(resourceNameJ instanceof JsonString)) {
error(path, viewDefinition, "resource must be a string", IssueType.INVALID);
} else {
resourceName = resourceNameJ.asString();
if (!context.getResourceNamesAsSet().contains(resourceName)) {
error(path+".name", nameJ, "The name '"+resourceName+"' is not a valid resource", IssueType.BUSINESSRULE);
} else {
int i = 0;
if (checkAllObjects(path, viewDefinition, "constant")) {
for (JsonObject constant : viewDefinition.getJsonObjects("constant")) {
checkConstant(path+".constant["+i+"]", constant);
i++;
}
}
i = 0;
if (checkAllObjects(path, viewDefinition, "where")) {
for (JsonObject where : viewDefinition.getJsonObjects("where")) {
checkWhere(viewDefinition, path+".where["+i+"]", where);
i++;
}
}
TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName);
i = 0;
if (checkAllObjects(path, viewDefinition, "select")) {
for (JsonObject select : viewDefinition.getJsonObjects("select")) {
columns.addAll(checkSelect(viewDefinition, path+".select["+i+"]", select, t));
i++;
}
if (i == 0) {
error(path, viewDefinition, "No select statements found", IssueType.REQUIRED);
}
}
}
}
}
private List<Column> checkSelect(JsonObject vd, String path, JsonObject select, TypeDetails t) {
List<Column> columns = new ArrayList<>();
select.setUserData("columns", columns);
checkProperties(select, path, "column", "select", "forEach", "forEachOrNull", "unionAll");
if (select.has("forEach")) {
t = checkForEach(vd, path, select, select.get("forEach"), t);
} else if (select.has("forEachOrNull")) {
t = checkForEachOrNull(vd, path, select, select.get("forEachOrNull"), t);
}
if (t != null) {
if (select.has("column")) {
JsonElement a = select.get("column");
if (!(a instanceof JsonArray)) {
error(path+".column", a, "column is not an array", IssueType.INVALID);
} else {
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID);
} else {
columns.add(checkColumn(vd, path+".column["+i+"]", (JsonObject) e, t));
}
}
}
}
if (select.has("select")) {
JsonElement a = select.get("select");
if (!(a instanceof JsonArray)) {
error(path+".select", a, "select is not an array", IssueType.INVALID);
} else {
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID);
} else {
columns.addAll(checkSelect(vd, path+".select["+i+"]", (JsonObject) e, t));
}
}
}
}
if (select.has("unionAll")) {
columns.addAll(checkUnion(vd, path, select, select.get("unionAll"), t));
}
if (columns.isEmpty()) {
error(path, select, "The select has no columns or selects", IssueType.REQUIRED);
} else {
checkColumnNamesUnique(select, path, columns);
}
}
return columns;
}
private void checkColumnNamesUnique(JsonObject select, String path, List<Column> columns) {
Set<String> names = new HashSet<>();
for (Column col : columns) {
if (col != null) {
if (!names.contains(col.getName())) {
names.add(col.getName());
} else if (!col.isDuplicateReported()) {
col.setDuplicateReported(true);
error(path, select, "Duplicate Column Name '"+col.getName()+"'", IssueType.BUSINESSRULE);
}
}
}
}
private List<Column> checkUnion(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
JsonElement a = focus.get("unionAll");
if (!(a instanceof JsonArray)) {
error(path+".unionAll", a, "union is not an array", IssueType.INVALID);
return null;
} else {
List<List<Column>> unionColumns = new ArrayList<>();
int i = 0;
for (JsonElement e : ((JsonArray) a)) {
if (!(e instanceof JsonObject)) {
error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID);
} else {
unionColumns.add(checkSelect(vd, path+".unionAll["+i+"]", (JsonObject) e, t));
}
i++;
}
if (i < 2) {
warning(path+".unionAll", a, "unionAll should have more than one item");
}
if (unionColumns.size() > 1) {
List<Column> columns = unionColumns.get(0);
for (int ic = 1; ic < unionColumns.size(); ic++) {
String diff = columnDiffs(columns, unionColumns.get(ic));
if (diff != null) {
error(path+".unionAll["+i+"]", ((JsonArray) a).get(ic), "unionAll["+i+"] column definitions do not match: "+diff, IssueType.INVALID);
}
}
a.setUserData("colunms", columns);
return columns;
}
}
return null;
}
private String columnDiffs(List<Column> list1, List<Column> list2) {
if (list1.size() == list2.size()) {
for (int i = 0; i < list1.size(); i++) {
if (list1.get(i) == null || list2.get(i) == null) {
return null; // just suppress any addition errors
}
String diff = list1.get(i).diff(list2.get(i));
if (diff != null) {
return diff+" at #"+i;
}
}
return null;
} else {
return "Column counts differ: "+list1.size()+" vs "+list2.size();
}
}
private Column checkColumn(JsonObject vd, String path, JsonObject column, TypeDetails t) {
checkProperties(column, path, "path", "name", "description", "collection", "type", "tag");
if (!column.has("path")) {
error(path, column, "no path found", IssueType.INVALID);
} else {
JsonElement expression = column.get("path");
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
} else {
String expr = expression.asString();
List<IssueMessage> warnings = new ArrayList<>();
TypeDetails td = null;
ExpressionNode node = null;
try {
node = fpe.parse(expr);
column.setUserData("path", node);
td = fpe.checkOnTypes(vd, resourceName, t, node, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null && node != null) {
for (IssueMessage s : warnings) {
warning(path+".path", expression, s.getMessage());
}
String columnName = null;
JsonElement nameJ = column.get("name");
if (nameJ != null) {
if (nameJ instanceof JsonString) {
columnName = nameJ.asString();
if (!isValidName(columnName)) {
error(path+".name", nameJ, "The name '"+columnName+"' is not valid", IssueType.VALUE);
}
} else {
error(path+".name", nameJ, "name must be a string", IssueType.INVALID);
}
}
if (columnName == null) {
List<String> names = node.getDistalNames();
if (names.size() == 1 && names.get(0) != null) {
columnName = names.get(0);
if (!isValidName(columnName)) {
error(path+".path", expression, "A column name is required. The natural name to chose is '"+columnName+"' (from the path)", IssueType.INVARIANT);
} else {
error(path, column, "A column name is required", IssueType.REQUIRED);
}
} else {
error(path, column, "A column name is required", IssueType.REQUIRED);
}
}
// ok, name is sorted!
if (columnName != null) {
column.setUserData("name", columnName);
boolean isColl = false;
if (column.has("collection")) {
JsonElement collectionJ = column.get("collection");
if (!(collectionJ instanceof JsonBoolean)) {
error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID);
} else {
boolean collection = collectionJ.asJsonBoolean().asBoolean();
if (collection) {
isColl = true;
}
}
}
if (isColl) {
if (td.getCollectionStatus() == CollectionStatus.SINGLETON) {
hint(path, column, "collection is true, but the path statement(s) ('"+expr+"') can only return single values for the column '"+columnName+"'");
}
if (supportsArrays == TrueFalseOrUnknown.UNKNOWN) {
warning(path, expression, "The column '"+columnName+"' is defined as a collection, but collections are not supported in all execution contexts");
} else if (supportsArrays == TrueFalseOrUnknown.FALSE) {
if (td.getCollectionStatus() == CollectionStatus.SINGLETON) {
warning(path, expression, "The column '"+columnName+"' is defined as a collection, but this is not allowed in the current execution context. Note that the path '"+expr+"' can only return a single value");
} else {
warning(path, expression, "The column '"+columnName+"' is defined as a collection, but this is not allowed in the current execution context. Note that the path '"+expr+"' can return a collection of values");
}
}
} else {
if (td.getCollectionStatus() != CollectionStatus.SINGLETON) {
warning(path, column, "This column is not defined as a collection, but the path statement '"+expr+"' might return multiple values for the column '"+columnName+"' for some inputs");
}
}
Set<String> types = new HashSet<>();
if (node.isNullSet()) {
types.add("null");
} else {
// ok collection is sorted
for (String type : td.getTypes()) {
types.add(simpleType(type));
}
JsonElement typeJ = column.get("type");
if (typeJ != null) {
if (typeJ instanceof JsonString) {
String type = typeJ.asString();
if (!td.hasType(type)) {
error(path+".type", typeJ, "The path expression ('"+expr+"') does not return a value of the type '"+type+"' - found "+td.describe(), IssueType.VALUE);
} else {
types.clear();
types.add(simpleType(type));
}
} else {
error(path+".type", typeJ, "type must be a string", IssueType.INVALID);
}
}
}
if (types.size() != 1) {
error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE);
} else {
String type = types.iterator().next();
boolean ok = false;
if (!isSimpleType(type) && !"null".equals(type)) {
if (supportsComplexTypes == TrueFalseOrUnknown.UNKNOWN) {
warning(path, expression, "Column from path '"+expr+"' is a complex type ('"+type+"'). This is not supported in some Runners");
} else if (supportsComplexTypes == TrueFalseOrUnknown.FALSE) {
error(path, expression, "Column from path '"+expr+"' is a complex type ('"+type+"') but this is not allowed in this context", IssueType.BUSINESSRULE);
} else {
ok = true;
}
} else {
ok = true;
}
if (ok) {
Column col = new Column(columnName, isColl, type, kindForType(type));
column.setUserData("column", col);
return col;
}
}
}
}
}
}
return null;
}
private ColumnKind kindForType(String type) {
switch (type) {
case "null": return ColumnKind.Null;
case "dateTime": return ColumnKind.DateTime;
case "boolean": return ColumnKind.Boolean;
case "integer": return ColumnKind.Integer;
case "decimal": return ColumnKind.Decimal;
case "string": return ColumnKind.String;
case "canonical": return ColumnKind.String;
case "url": return ColumnKind.String;
case "uri": return ColumnKind.String;
case "oid": return ColumnKind.String;
case "uuid": return ColumnKind.String;
case "id": return ColumnKind.String;
case "code": return ColumnKind.String;
case "base64Binary": return ColumnKind.Binary;
case "time": return ColumnKind.Time;
default: return ColumnKind.Complex;
}
}
private boolean isSimpleType(String type) {
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary", "id", "code", "date", "time", "canonical");
}
private String simpleType(String type) {
type = type.replace("http://hl7.org/fhirpath/System.", "").replace("http://hl7.org/fhir/StructureDefinition/", "");
if (Utilities.existsInList(type, "date", "dateTime", "instant")) {
return "dateTime";
}
if (Utilities.existsInList(type, "Boolean", "boolean")) {
return "boolean";
}
if (Utilities.existsInList(type, "Integer", "integer", "integer64")) {
return "integer";
}
if (Utilities.existsInList(type, "Decimal", "decimal")) {
return "decimal";
}
if (Utilities.existsInList(type, "String", "string", "code")) {
return "string";
}
if (Utilities.existsInList(type, "Time", "time")) {
return "time";
}
if (Utilities.existsInList(type, "base64Binary")) {
return "base64Binary";
}
return type;
}
private TypeDetails checkForEach(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID);
return null;
} else {
String expr = expression.asString();
List<IssueMessage> warnings = new ArrayList<>();
TypeDetails td = null;
try {
ExpressionNode n = fpe.parse(expr);
focus.setUserData("forEach", n);
td = fpe.checkOnTypes(vd, resourceName, t, n, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null) {
for (IssueMessage s : warnings) {
warning(path+".forEach", expression, s.getMessage());
}
}
return td;
}
}
private TypeDetails checkForEachOrNull(JsonObject vd, String path, JsonObject focus, JsonElement expression, TypeDetails t) {
if (!(expression instanceof JsonString)) {
error(path+".forEachOrNull", expression, "forEachOrNull is not a string", IssueType.INVALID);
return null;
} else {
String expr = expression.asString();
List<IssueMessage> warnings = new ArrayList<>();
TypeDetails td = null;
try {
ExpressionNode n = fpe.parse(expr);
focus.setUserData("forEachOrNull", n);
td = fpe.checkOnTypes(vd, resourceName, t, n, warnings);
} catch (Exception e) {
error(path, expression, e.getMessage(), IssueType.INVALID);
}
if (td != null) {
for (IssueMessage s : warnings) {
warning(path+".forEachOrNull", expression, s.getMessage());
}
}
return td;
}
}
private void checkConstant(String path, JsonObject constant) {
checkProperties(constant, path, "name", "valueBase64Binary", "valueBoolean", "valueCanonical", "valueCode", "valueDate", "valueDateTime", "valueDecimal", "valueId", "valueInstant", "valueInteger", "valueInteger64", "valueOid", "valueString", "valuePositiveInt", "valueTime", "valueUnsignedInt", "valueUri", "valueUrl", "valueUuid");
JsonElement nameJ = constant.get("name");
if (nameJ == null) {
error(path, constant, "No name provided", IssueType.REQUIRED);
} else if (!(nameJ instanceof JsonString)) {
error(path, constant, "Name must be a string", IssueType.INVALID);
} else {
String name = constant.asString("name");
if (!isValidName(name)) {
error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT);
}
}
if (constant.has("valueBase64Binary")) {
checkIsString(path, constant, "valueBase64Binary", new Base64BinaryType());
} else if (constant.has("valueBoolean")) {
checkIsBoolean(path, constant, "valueBoolean", new BooleanType());
} else if (constant.has("valueCanonical")) {
checkIsString(path, constant, "valueCanonical", new CanonicalType());
} else if (constant.has("valueCode")) {
checkIsString(path, constant, "valueCode", new CodeType());
} else if (constant.has("valueDate")) {
checkIsString(path, constant, "valueDate", new DateType());
} else if (constant.has("valueDateTime")) {
checkIsString(path, constant, "valueDateTime", new DateTimeType());
} else if (constant.has("valueDecimal")) {
checkIsNumber(path, constant, "valueDecimal", new DecimalType());
} else if (constant.has("valueId")) {
checkIsString(path, constant, "valueId", new IdType());
} else if (constant.has("valueInstant")) {
checkIsString(path, constant, "valueInstant", new InstantType());
} else if (constant.has("valueInteger")) {
checkIsNumber(path, constant, "valueInteger", new IntegerType());
} else if (constant.has("valueOid")) {
checkIsString(path, constant, "valueOid", new OidType());
} else if (constant.has("valueString")) {
checkIsString(path, constant, "valueString", new StringType());
} else if (constant.has("valuePositiveInt")) {
checkIsNumber(path, constant, "valuePositiveInt", new PositiveIntType());
} else if (constant.has("valueTime")) {
checkIsString(path, constant, "valueTime", new TimeType());
} else if (constant.has("valueUnsignedInt")) {
checkIsNumber(path, constant, "valueUnsignedInt", new UnsignedIntType());
} else if (constant.has("valueUri")) {
checkIsString(path, constant, "valueUri", new UriType());
} else if (constant.has("valueUrl")) {
checkIsString(path, constant, "valueUrl", new UrlType());
} else if (constant.has("valueUuid")) {
checkIsString(path, constant, "valueUuid", new UuidType());
} else {
error(path, constant, "No value found", IssueType.REQUIRED);
}
}
private void checkIsString(String path, JsonObject constant, String name, PrimitiveType<?> value) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonString)) {
error(path+"."+name, j, name+" must be a string", IssueType.INVALID);
} else {
value.setValueAsString(j.asString());
constant.setUserData("value", value);
}
}
private void checkIsBoolean(String path, JsonObject constant, String name, PrimitiveType<?> value) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonBoolean)) {
error(path+"."+name, j, name+" must be a boolean", IssueType.INVALID);
} else {
value.setValueAsString(j.asString());
constant.setUserData("value", value);
}
}
private void checkIsNumber(String path, JsonObject constant, String name, PrimitiveType<?> value) {
JsonElement j = constant.get(name);
if (!(j instanceof JsonNumber)) {
error(path+"."+name, j, name+" must be a number", IssueType.INVALID);
} else {
value.setValueAsString(j.asString());
constant.setUserData("value", value);
}
}
private void checkWhere(JsonObject vd, String path, JsonObject where) {
checkProperties(where, path, "path", "description");
String expr = where.asString("path");
if (expr == null) {
error(path, where, "No path provided", IssueType.REQUIRED);
}
List<String> types = new ArrayList<>();
List<IssueMessage> warnings = new ArrayList<>();
types.add(resourceName);
TypeDetails td = null;
try {
ExpressionNode n = fpe.parse(expr);
where.setUserData("path", n);
td = fpe.checkOnTypes(vd, resourceName, types, n, warnings);
} catch (Exception e) {
error(path, where.get("path"), e.getMessage(), IssueType.INVALID);
}
if (td != null) {
if (td.getCollectionStatus() != CollectionStatus.SINGLETON || td.getTypes().size() != 1 || !td.hasType("boolean")) {
error(path+".path", where.get("path"), "A where path must return a boolean, but the expression "+expr+" returns a "+td.describe(), IssueType.BUSINESSRULE);
} else {
for (IssueMessage s : warnings) {
warning(path+".path", where.get("path"), s.getMessage());
}
}
}
}
private void checkProperties(JsonObject obj, String path, String... names) {
for (JsonProperty p : obj.getProperties()) {
boolean nameOk = "extension".equals(p.getName());
for (String name : names) {
nameOk = nameOk || name.equals(p.getName());
}
if (!nameOk) {
error(path+"."+p.getName(), p.getValue(), "Unknown JSON property "+p.getName(), IssueType.UNKNOWN);
}
}
}
private boolean isValidName(String name) {
boolean first = true;
for (char c : name.toCharArray()) {
if (!(Character.isAlphabetic(c) || Character.isDigit(c) || (!first && c == '_'))) {
return false;
}
first = false;
}
return true;
}
private boolean checkAllObjects(String path, JsonObject focus, String name) {
if (!focus.has(name)) {
return true;
} else if (!(focus.get(name) instanceof JsonArray)) {
error(path+"."+name, focus.get(name), name+" must be an array", IssueType.INVALID);
return false;
} else {
JsonArray arr = focus.getJsonArray(name);
int i = 0;
boolean ok = true;
for (JsonElement e : arr) {
if (!(e instanceof JsonObject)) {
error(path+"."+name+"["+i+"]", e, name+"["+i+"] must be an object", IssueType.INVALID);
ok = false;
}
}
return ok;
}
}
private void error(String path, JsonElement e, String issue, IssueType type) {
ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, type, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.ERROR);
issues.add(vm);
}
private void warning(String path, JsonElement e, String issue) {
ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.WARNING);
issues.add(vm);
}
private void hint(String path, JsonElement e, String issue) {
ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.INFORMATION);
issues.add(vm);
}
public void dump() {
for (ValidationMessage vm : issues) {
System.out.println(vm.summary());
}
}
public void check() {
if (!isOk()) {
throw new FHIRException("View Definition is not valid");
}
}
public String getName() {
return name;
}
public List<ValidationMessage> getIssues() {
return issues;
}
public boolean isOk() {
boolean ok = true;
for (ValidationMessage vm : issues) {
if (vm.isError()) {
ok = false;
}
}
return ok;
}
}

View File

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

View File

@ -117,6 +117,10 @@ public class FHIRPathTests {
return TestingUtilities.context().fetchResource(ValueSet.class, url);
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private static FHIRPathEngine fp;

View File

@ -249,6 +249,11 @@ public class SnapShotGenerationTests {
return null;
}
@Override
public boolean isPrimitiveType(String name) {
StructureDefinition sd = TestingUtilities.context().fetchTypeDefinition(name);
return (sd != null) && (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE);
}
}
private static class SnapShotGenerationTestsContext implements IEvaluationContext {
@ -386,6 +391,10 @@ public class SnapShotGenerationTests {
throw new Error("Not implemented yet");
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private static FHIRPathEngine fp;

View File

@ -58,26 +58,26 @@ public class FHIRLexer {
private boolean allowDoubleQuotes;
public FHIRLexer(String source, String name) throws FHIRLexerException {
this.source = source == null ? "" : source;
this.source = source == null ? "" : Utilities.stripBOM(source);
this.name = name == null ? "??" : name;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, int i) throws FHIRLexerException {
this.source = source;
this.source = Utilities.stripBOM(source);
this.cursor = i;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, int i, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source;
this.source = Utilities.stripBOM(source);
this.cursor = i;
this.allowDoubleQuotes = allowDoubleQuotes;
currentLocation = new SourceLocation(1, 1);
next();
}
public FHIRLexer(String source, String name, boolean metadataFormat, boolean allowDoubleQuotes) throws FHIRLexerException {
this.source = source == null ? "" : source;
this.source = source == null ? "" : Utilities.stripBOM(source);
this.name = name == null ? "??" : name;
this.metadataFormat = metadataFormat;
this.allowDoubleQuotes = allowDoubleQuotes;
@ -187,7 +187,7 @@ public class FHIRLexer {
cursor++;
} else
while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') ||
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-'))
(source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-' || source.charAt(cursor) == '_'))
cursor++;
current = source.substring(currentStart, cursor);
} else if (ch == '/') {
@ -455,7 +455,7 @@ public class FHIRLexer {
i = i + 4;
break;
default:
throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i), currentLocation);
throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation);
}
} else {
b.append(ch);
@ -499,12 +499,12 @@ public class FHIRLexer {
break;
case 'u':
i++;
int uc = Integer.parseInt(s.substring(i, i+4), 16);
int uc = Integer.parseInt(s.substring(i, i+4), 32);
b.append(Character.toString(uc));
i = i + 4;
break;
default:
throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i), currentLocation);
throw new FHIRLexerException("Unknown FHIRPath character escape \\"+s.charAt(i), currentLocation);
}
} else {
b.append(ch);