Clone SQL on FHIR engine to R4, and update FHIRPath engine based on R5 current code
This commit is contained in:
parent
d883dd37c2
commit
d58188748c
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>();
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.hl7.fhir.r4.utils.sql;
|
||||
|
||||
public enum ColumnKind {
|
||||
String, DateTime, Integer, Decimal, Binary, Time, Boolean, Complex, Null
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue