This commit is contained in:
Grahame Grieve 2019-08-16 07:19:21 +10:00
commit 55259ab8dc
19 changed files with 1467 additions and 454 deletions

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -82,6 +82,7 @@ public class Property {
this.definition = definition; this.definition = definition;
this.minCardinality = minCardinality; this.minCardinality = minCardinality;
this.maxCardinality = maxCardinality; this.maxCardinality = maxCardinality;
if (value != null)
this.values.add(value); this.values.add(value);
} }
@ -156,6 +157,8 @@ public class Property {
this.structure = structure; this.structure = structure;
} }
public boolean isList() {
return maxCardinality > 1;
}
} }

View File

@ -0,0 +1,964 @@
package org.hl7.fhir.dstu3.utils;
/*-
* #%L
* org.hl7.fhir.dstu3
* %%
* Copyright (C) 2014 - 2019 Health Level 7
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.*;
import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus;
import org.hl7.fhir.utilities.graphql.Package;
import org.hl7.fhir.utilities.graphql.Operation.OperationType;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hl7.fhir.utilities.graphql.IGraphQLStorageServices.ReferenceResolution;
public class GraphQLEngine implements IGraphQLEngine {
public static class SearchEdge extends Base {
private BundleEntryComponent be;
private String type;
SearchEdge(String type, BundleEntryComponent be) {
this.type = type;
this.be = be;
}
@Override
public String fhirType() {
return type;
}
@Override
protected void listChildren(List<Property> result) {
throw new Error("Not Implemented");
}
@Override
public String getIdBase() {
throw new Error("Not Implemented");
}
@Override
public void setIdBase(String value) {
throw new Error("Not Implemented");
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 3357091: /*mode*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasMode() ? be.getSearch().getModeElement() : null);
case 109264530: /*score*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasScore() ? be.getSearch().getScoreElement() : null);
case -341064690: /*resource*/ return new Property(_name, "resource", "n/a", 0, 1, be.hasResource() ? be.getResource() : null);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
}
public static class SearchWrapper extends Base {
private Bundle bnd;
private String type;
private Map<String, String> map;
SearchWrapper(String type, Bundle bnd) throws FHIRException {
this.type = type;
this.bnd = bnd;
for (BundleLinkComponent bl : bnd.getLink())
if (bl.getRelation().equals("self"))
map = parseURL(bl.getUrl());
}
@Override
public String fhirType() {
return type;
}
@Override
protected void listChildren(List<Property> result) {
throw new Error("Not Implemented");
}
@Override
public String getIdBase() {
throw new Error("Not Implemented");
}
@Override
public void setIdBase(String value) {
throw new Error("Not Implemented");
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 97440432: /*first*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case -1273775369: /*previous*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 3377907: /*next*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 3314326: /*last*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 94851343: /*count*/ return new Property(_name, "integer", "n/a", 0, 1, bnd.getTotalElement());
case -1019779949:/*offset*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("search-offset"));
case 860381968: /*pagesize*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("_count"));
case 96356950: /*edges*/ return new Property(_name, "edge", "n/a", 0, Integer.MAX_VALUE, getEdges());
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
private List<Base> getEdges() {
List<Base> list = new ArrayList<>();
for (BundleEntryComponent be : bnd.getEntry())
list.add(new SearchEdge(type.substring(0, type.length() - 10) + "Edge", be));
return list;
}
private Base extractParam(String name) throws FHIRException {
return map != null ? new IntegerType(map.get(name)) : null;
}
private Map<String, String> parseURL(String url) throws FHIRException {
try {
Map<String, String> map = new HashMap<String, String>();
String[] pairs = url.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key;
key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
map.put(key, value);
}
return map;
} catch (UnsupportedEncodingException e) {
throw new FHIRException(e);
}
}
private Base extractLink(String _name) throws FHIRException {
for (BundleLinkComponent bl : bnd.getLink()) {
if (bl.getRelation().equals(_name)) {
Map<String, String> map = parseURL(bl.getUrl());
return new StringType(map.get("search-id")+':'+map.get("search-offset"));
}
}
return null;
}
}
private IWorkerContext context;
public GraphQLEngine(IWorkerContext context) {
super();
this.context = context;
}
/**
* for the host to pass context into and get back on the reference resolution interface
*/
private Object appInfo;
/**
* the focus resource - if (there instanceof one. if (there isn"t,) there instanceof no focus
*/
private Resource focus;
/**
* The package that describes the graphQL to be executed, operation name, and variables
*/
private Package graphQL;
/**
* where the output from executing the query instanceof going to go
*/
private ObjectValue output;
/**
* Application provided reference resolution services
*/
private IGraphQLStorageServices services;
// internal stuff
private Map<String, Argument> workingVariables = new HashMap<String, Argument>();
private FHIRPathEngine fpe;
private ExpressionNode magicExpression;
public void execute() throws EGraphEngine, EGraphQLException, FHIRException {
if (graphQL == null)
throw new EGraphEngine("Unable to process graphql - graphql document missing");
fpe = new FHIRPathEngine(this.context);
magicExpression = new ExpressionNode(0);
output = new ObjectValue();
Operation op = null;
// todo: initial conditions
if (!Utilities.noString(graphQL.getOperationName())) {
op = graphQL.getDocument().operation(graphQL.getOperationName());
if (op == null)
throw new EGraphEngine("Unable to find operation \""+graphQL.getOperationName()+"\"");
} else if ((graphQL.getDocument().getOperations().size() == 1))
op = graphQL.getDocument().getOperations().get(0);
else
throw new EGraphQLException("No operation name provided, so expected to find a single operation");
if (op.getOperationType() == OperationType.qglotMutation)
throw new EGraphQLException("Mutation operations are not supported (yet)");
checkNoDirectives(op.getDirectives());
processVariables(op);
if (focus == null)
processSearch(output, op.getSelectionSet(), false, "");
else
processObject(focus, focus, output, op.getSelectionSet(), false, "");
}
private boolean checkBooleanDirective(Directive dir) throws EGraphQLException {
if (dir.getArguments().size() != 1)
throw new EGraphQLException("Unable to process @"+dir.getName()+": expected a single argument \"if\"");
if (!dir.getArguments().get(0).getName().equals("if"))
throw new EGraphQLException("Unable to process @"+dir.getName()+": expected a single argument \"if\"");
List<Value> vl = resolveValues(dir.getArguments().get(0), 1);
return vl.get(0).toString().equals("true");
}
private boolean checkDirectives(List<Directive> directives) throws EGraphQLException {
Directive skip = null;
Directive include = null;
for (Directive dir : directives) {
if (dir.getName().equals("skip")) {
if ((skip == null))
skip = dir;
else
throw new EGraphQLException("Duplicate @skip directives");
} else if (dir.getName().equals("include")) {
if ((include == null))
include = dir;
else
throw new EGraphQLException("Duplicate @include directives");
}
else if (!Utilities.existsInList(dir.getName(), "flatten", "first", "singleton", "slice"))
throw new EGraphQLException("Directive \""+dir.getName()+"\" instanceof not recognised");
}
if ((skip != null && include != null))
throw new EGraphQLException("Cannot mix @skip and @include directives");
if (skip != null)
return !checkBooleanDirective(skip);
else if (include != null)
return checkBooleanDirective(include);
else
return true;
}
private void checkNoDirectives(List<Directive> directives) {
}
private boolean targetTypeOk(List<Argument> arguments, IBaseResource dest) throws EGraphQLException {
List<String> list = new ArrayList<String>();
for (Argument arg : arguments) {
if ((arg.getName().equals("type"))) {
List<Value> vl = resolveValues(arg);
for (Value v : vl)
list.add(v.toString());
}
}
if (list.size() == 0)
return true;
else
return list.indexOf(dest.fhirType()) > -1;
}
private boolean hasExtensions(Base obj) {
if (obj instanceof BackboneElement)
return ((BackboneElement) obj).getExtension().size() > 0 || ((BackboneElement) obj).getModifierExtension().size() > 0;
else if (obj instanceof DomainResource)
return ((DomainResource)obj).getExtension().size() > 0 || ((DomainResource)obj).getModifierExtension().size() > 0;
else if (obj instanceof Element)
return ((Element)obj).getExtension().size() > 0;
else
return false;
}
private boolean passesExtensionMode(Base obj, boolean extensionMode) {
if (!obj.isPrimitive())
return !extensionMode;
else if (extensionMode)
return !Utilities.noString(obj.getIdBase()) || hasExtensions(obj);
else
return obj.primitiveValue() != "";
}
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>();
if (values.size() > 0) {
int count = Integer.MAX_VALUE;
int offset = 0;
StringBuilder fp = new StringBuilder();
for (Argument arg : arguments) {
List<Value> vl = resolveValues(arg);
if ((vl.size() != 1))
throw new EGraphQLException("Incorrect number of arguments");
if (values.get(0).isPrimitive())
throw new EGraphQLException("Attempt to use a filter ("+arg.getName()+") on a primtive type ("+prop.getTypeCode()+")");
if ((arg.getName().equals("fhirpath")))
fp.append(" and "+vl.get(0).toString());
else if ((arg.getName().equals("_count")))
count = Integer.valueOf(vl.get(0).toString());
else if ((arg.getName().equals("_offset")))
offset = Integer.valueOf(vl.get(0).toString());
else {
Property p = values.get(0).getNamedProperty(arg.getName());
if (p == null)
throw new EGraphQLException("Attempt to use an unknown filter ("+arg.getName()+") on a type ("+prop.getTypeCode()+")");
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
}
}
int i = 0;
int t = 0;
if (fp.length() == 0)
for (Base v : values) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v);
t++;
if (t >= count)
break;
}
i++;
} else {
ExpressionNode node = fpe.parse(fp.toString().substring(5));
for (Base v : values) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v);
t++;
if (t >= count)
break;
}
i++;
}
}
}
return result;
}
private List<Resource> filterResources(Argument fhirpath, Bundle bnd) throws EGraphQLException, FHIRException {
List<Resource> result = new ArrayList<Resource>();
if (bnd.getEntry().size() > 0) {
if ((fhirpath == null))
for (BundleEntryComponent be : bnd.getEntry())
result.add(be.getResource());
else {
FHIRPathEngine fpe = new FHIRPathEngine(context);
ExpressionNode node = fpe.parse(getSingleValue(fhirpath));
for (BundleEntryComponent be : bnd.getEntry())
if (fpe.evaluateToBoolean(null, be.getResource(), be.getResource(), node))
result.add(be.getResource());
}
}
return result;
}
private List<Resource> filterResources(Argument fhirpath, List<IBaseResource> list) throws EGraphQLException, FHIRException {
List<Resource> result = new ArrayList<>();
if (list.size() > 0) {
if ((fhirpath == null))
for (IBaseResource v : list)
result.add((Resource) v);
else {
FHIRPathEngine fpe = new FHIRPathEngine(context);
ExpressionNode node = fpe.parse(getSingleValue(fhirpath));
for (IBaseResource v : list)
if (fpe.evaluateToBoolean(null, (Resource)v, (Resource)v, node))
result.add((Resource) v);
}
}
return result;
}
private boolean hasArgument(List<Argument> arguments, String name, String value) {
for (Argument arg : arguments)
if ((arg.getName().equals(name)) && arg.hasValue(value))
return true;
return false;
}
private void processValues(Resource context, Selection sel, Property prop, ObjectValue target, List<Base> values, boolean extensionMode, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
boolean il = false;
Argument arg = null;
ExpressionNode expression = null;
if (sel.getField().hasDirective("slice")) {
Directive dir = sel.getField().directive("slice");
String s = ((StringValue) dir.getArguments().get(0).getValues().get(0)).getValue();
if (s.equals("$index"))
expression = magicExpression;
else
expression = fpe.parse(s);
}
if (sel.getField().hasDirective("flatten")) // special: instruction to drop this node...
il = prop.isList() && !sel.getField().hasDirective("first");
else if (sel.getField().hasDirective("first")) {
if (expression != null)
throw new FHIRException("You cannot mix @slice and @first");
arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), inheritedList));
} else if (expression == null)
arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), prop.isList() || inheritedList));
int index = 0;
for (Base value : values) {
String ss = "";
if (expression != null) {
if (expression == magicExpression)
ss = suffix+'.'+Integer.toString(index);
else
ss = suffix+'.'+fpe.evaluateToString(null, null, value, expression);
if (!sel.getField().hasDirective("flatten"))
arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), prop.isList() || inheritedList));
}
if (value.isPrimitive() && !extensionMode) {
if (!sel.getField().getSelectionSet().isEmpty())
throw new EGraphQLException("Encountered a selection set on a scalar field type");
processPrimitive(arg, value);
} else {
if (sel.getField().getSelectionSet().isEmpty())
throw new EGraphQLException("No Fields selected on a complex object");
if (arg == null)
processObject(context, value, target, sel.getField().getSelectionSet(), il, ss);
else {
ObjectValue n = new ObjectValue();
arg.addValue(n);
processObject(context, value, n, sel.getField().getSelectionSet(), il, ss);
}
}
if (sel.getField().hasDirective("first"))
return;
index++;
}
}
private void processVariables(Operation op) throws EGraphQLException {
for (Variable varRef : op.getVariables()) {
Argument varDef = null;
for (Argument v : graphQL.getVariables())
if (v.getName().equals(varRef.getName()))
varDef = v;
if (varDef != null)
workingVariables.put(varRef.getName(), varDef); // todo: check type?
else if (varRef.getDefaultValue() != null)
workingVariables.put(varRef.getName(), new Argument(varRef.getName(), varRef.getDefaultValue()));
else
throw new EGraphQLException("No value found for variable ");
}
}
private boolean isPrimitive(String typename) {
return Utilities.existsInList(typename, "boolean", "integer", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "url", "canonical");
}
private boolean isResourceName(String name, String suffix) {
if (!name.endsWith(suffix))
return false;
name = name.substring(0, name.length()-suffix.length());
return context.getResourceNamesAsSet().contains(name);
}
private void processObject(Resource context, Base source, ObjectValue target, List<Selection> selection, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
for (Selection sel : selection) {
if (sel.getField() != null) {
if (checkDirectives(sel.getField().getDirectives())) {
Property prop = source.getNamedProperty(sel.getField().getName());
if ((prop == null) && sel.getField().getName().startsWith("_"))
prop = source.getNamedProperty(sel.getField().getName().substring(1));
if (prop == null) {
if ((sel.getField().getName().equals("resourceType") && source instanceof Resource))
target.addField("resourceType", listStatus(sel.getField(), false)).addValue(new StringValue(source.fhirType()));
else if ((sel.getField().getName().equals("resource") && source.fhirType().equals("Reference")))
processReference(context, source, sel.getField(), target, inheritedList, suffix);
else if ((sel.getField().getName().equals("resource") && source.fhirType().equals("canonical")))
processCanonicalReference(context, source, sel.getField(), target, inheritedList, suffix);
else if (isResourceName(sel.getField().getName(), "List") && (source instanceof Resource))
processReverseReferenceList((Resource) source, sel.getField(), target, inheritedList, suffix);
else if (isResourceName(sel.getField().getName(), "Connection") && (source instanceof Resource))
processReverseReferenceSearch((Resource) source, sel.getField(), target, inheritedList, suffix);
else
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
} else {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
}
}
} else if (sel.getInlineFragment() != null) {
if (checkDirectives(sel.getInlineFragment().getDirectives())) {
if (Utilities.noString(sel.getInlineFragment().getTypeCondition()))
throw new EGraphQLException("Not done yet - inline fragment with no type condition"); // cause why? why instanceof it even valid?
if (source.fhirType().equals(sel.getInlineFragment().getTypeCondition()))
processObject(context, source, target, sel.getInlineFragment().getSelectionSet(), inheritedList, suffix);
}
} else if (checkDirectives(sel.getFragmentSpread().getDirectives())) {
Fragment fragment = graphQL.getDocument().fragment(sel.getFragmentSpread().getName());
if (fragment == null)
throw new EGraphQLException("Unable to resolve fragment "+sel.getFragmentSpread().getName());
if (Utilities.noString(fragment.getTypeCondition()))
throw new EGraphQLException("Not done yet - inline fragment with no type condition"); // cause why? why instanceof it even valid?
if (source.fhirType().equals(fragment.getTypeCondition()))
processObject(context, source, target, fragment.getSelectionSet(), inheritedList, suffix);
}
}
}
private void processPrimitive(Argument arg, Base value) {
String s = value.fhirType();
if (s.equals("integer") || s.equals("decimal") || s.equals("unsignedInt") || s.equals("positiveInt"))
arg.addValue(new NumberValue(value.primitiveValue()));
else if (s.equals("boolean"))
arg.addValue(new NameValue(value.primitiveValue()));
else
arg.addValue(new StringValue(value.primitiveValue()));
}
private void processReference(Resource context, Base source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (!(source instanceof Reference))
throw new EGraphQLException("Not done yet");
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
Reference ref = (Reference) source;
ReferenceResolution res = services.lookup(appInfo, context, ref);
if (res != null) {
if (targetTypeOk(field.getArguments(), res.getTarget())) {
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList));
ObjectValue obj = new ObjectValue();
arg.addValue(obj);
processObject((Resource)res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix);
}
}
else if (!hasArgument(field.getArguments(), "optional", "true"))
throw new EGraphQLException("Unable to resolve reference to "+ref.getReference());
}
private void processCanonicalReference(Resource context, Base source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
Reference ref = new Reference(source.primitiveValue());
ReferenceResolution res = services.lookup(appInfo, context, ref);
if (res != null) {
if (targetTypeOk(field.getArguments(), res.getTarget())) {
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList));
ObjectValue obj = new ObjectValue();
arg.addValue(obj);
processObject((Resource)res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix);
}
}
else if (!hasArgument(field.getArguments(), "optional", "true"))
throw new EGraphQLException("Unable to resolve reference to "+ref.getReference());
}
private ArgumentListStatus listStatus(Field field, boolean isList) {
if (field.hasDirective("singleton"))
return ArgumentListStatus.SINGLETON;
else if (isList)
return ArgumentListStatus.REPEATING;
else
return ArgumentListStatus.NOT_SPECIFIED;
}
private void processReverseReferenceList(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
List<IBaseResource> list = new ArrayList<>();
List<Argument> params = new ArrayList<>();
Argument parg = null;
for (Argument a : field.getArguments())
if (!(a.getName().equals("_reference")))
params.add(a);
else if ((parg == null))
parg = a;
else
throw new EGraphQLException("Duplicate parameter _reference");
if (parg == null)
throw new EGraphQLException("Missing parameter _reference");
Argument arg = new Argument();
params.add(arg);
arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list);
arg = null;
ObjectValue obj = null;
List<Resource> vl = filterResources(field.argument("fhirpath"), list);
if (!vl.isEmpty()) {
arg = target.addField(field.getAlias()+suffix, listStatus(field, true));
for (Resource v : vl) {
obj = new ObjectValue();
arg.addValue(obj);
processObject(v, v, obj, field.getSelectionSet(), inheritedList, suffix);
}
}
}
private void processReverseReferenceSearch(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
List<Argument> params = new ArrayList<Argument>();
Argument parg = null;
for (Argument a : field.getArguments())
if (!(a.getName().equals("_reference")))
params.add(a);
else if ((parg == null))
parg = a;
else
throw new EGraphQLException("Duplicate parameter _reference");
if (parg == null)
throw new EGraphQLException("Missing parameter _reference");
Argument arg = new Argument();
params.add(arg);
arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params);
Base bndWrapper = new SearchWrapper(field.getName(), bnd);
arg = target.addField(field.getAlias()+suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue();
arg.addValue(obj);
processObject(null, bndWrapper, obj, field.getSelectionSet(), inheritedList, suffix);
}
private void processSearch(ObjectValue target, List<Selection> selection, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
for (Selection sel : selection) {
if ((sel.getField() == null))
throw new EGraphQLException("Only field selections are allowed in this context");
checkNoDirectives(sel.getField().getDirectives());
if ((isResourceName(sel.getField().getName(), "")))
processSearchSingle(target, sel.getField(), inheritedList, suffix);
else if ((isResourceName(sel.getField().getName(), "List")))
processSearchSimple(target, sel.getField(), inheritedList, suffix);
else if ((isResourceName(sel.getField().getName(), "Connection")))
processSearchFull(target, sel.getField(), inheritedList, suffix);
}
}
private void processSearchSingle(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
String id = "";
for (Argument arg : field.getArguments())
if ((arg.getName().equals("id")))
id = getSingleValue(arg);
else
throw new EGraphQLException("Unknown/invalid parameter "+arg.getName());
if (Utilities.noString(id))
throw new EGraphQLException("No id found");
Resource res = (Resource) services.lookup(appInfo, field.getName(), id);
if (res == null)
throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found");
Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue();
arg.addValue(obj);
processObject(res, res, obj, field.getSelectionSet(), inheritedList, suffix);
}
private void processSearchSimple(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
List<IBaseResource> list = new ArrayList<>();
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list);
Argument arg = null;
ObjectValue obj = null;
List<Resource> vl = filterResources(field.argument("fhirpath"), list);
if (!vl.isEmpty()) {
arg = target.addField(field.getAlias()+suffix, listStatus(field, true));
for (Resource v : vl) {
obj = new ObjectValue();
arg.addValue(obj);
processObject(v, v, obj, field.getSelectionSet(), inheritedList, suffix);
}
}
}
private void processSearchFull(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null)
throw new EGraphQLException("Resource Referencing services not provided");
List<Argument> params = new ArrayList<Argument>();
Argument carg = null;
for ( Argument arg : field.getArguments())
if (arg.getName().equals("cursor"))
carg = arg;
else
params.add(arg);
if ((carg != null)) {
params.clear();;
String[] parts = getSingleValue(carg).split(":");
params.add(new Argument("search-id", new StringValue(parts[0])));
params.add(new Argument("search-offset", new StringValue(parts[1])));
}
Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params);
SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd);
Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue();
arg.addValue(obj);
processObject(null, bndWrapper, obj, field.getSelectionSet(), inheritedList, suffix);
}
private String getSingleValue(Argument arg) throws EGraphQLException {
List<Value> vl = resolveValues(arg, 1);
if (vl.size() == 0)
return "";
return vl.get(0).toString();
}
private List<Value> resolveValues(Argument arg) throws EGraphQLException {
return resolveValues(arg, -1, "");
}
private List<Value> resolveValues(Argument arg, int max) throws EGraphQLException {
return resolveValues(arg, max, "");
}
private List<Value> resolveValues(Argument arg, int max, String vars) throws EGraphQLException {
List<Value> result = new ArrayList<Value>();
for (Value v : arg.getValues()) {
if (! (v instanceof VariableValue))
result.add(v);
else {
if (vars.contains(":"+v.toString()+":"))
throw new EGraphQLException("Recursive reference to variable "+v.toString());
Argument a = workingVariables.get(v.toString());
if (a == null)
throw new EGraphQLException("No value found for variable \""+v.toString()+"\" in \""+arg.getName()+"\"");
List<Value> vl = resolveValues(a, -1, vars+":"+v.toString()+":");
result.addAll(vl);
}
}
if ((max != -1 && result.size() > max))
throw new EGraphQLException("Only "+Integer.toString(max)+" values are allowed for \""+arg.getName()+"\", but "+Integer.toString(result.size())+" enoucntered");
return result;
}
public Object getAppInfo() {
return appInfo;
}
public void setAppInfo(Object appInfo) {
this.appInfo = appInfo;
}
public Resource getFocus() {
return focus;
}
@Override
public void setFocus(IBaseResource focus) {
this.focus = (Resource) focus;
}
public Package getGraphQL() {
return graphQL;
}
@Override
public void setGraphQL(Package graphQL) {
this.graphQL = graphQL;
}
public ObjectValue getOutput() {
return output;
}
public IGraphQLStorageServices getServices() {
return services;
}
@Override
public void setServices(IGraphQLStorageServices services) {
this.services = services;
}
//
//{ GraphQLSearchWrapper }
//
//constructor GraphQLSearchWrapper.Create(bundle : Bundle);
//var
// s : String;
//{
// inherited Create;
// FBundle = bundle;
// s = bundle_List.Matches["self"];
// FParseMap = TParseMap.create(s.Substring(s.IndexOf("?")+1));
//}
//
//destructor GraphQLSearchWrapper.Destroy;
//{
// FParseMap.free;
// FBundle.Free;
// inherited;
//}
//
//function GraphQLSearchWrapper.extractLink(name: String): String;
//var
// s : String;
// pm : TParseMap;
//{
// s = FBundle_List.Matches[name];
// if (s == "")
// result = null
// else
// {
// pm = TParseMap.create(s.Substring(s.IndexOf("?")+1));
// try
// result = String.Create(pm.GetVar("search-id")+":"+pm.GetVar("search-offset"));
// finally
// pm.Free;
// }
// }
//}
//
//function GraphQLSearchWrapper.extractParam(name: String; int : boolean): Base;
//var
// s : String;
//{
// s = FParseMap.GetVar(name);
// if (s == "")
// result = null
// else if (int)
// result = Integer.Create(s)
// else
// result = String.Create(s);
//}
//
//function GraphQLSearchWrapper.fhirType(): String;
//{
// result = "*Connection";
//}
//
// // http://test.fhir.org/r4/Patient?_format==text/xhtml&search-id==77c97e03-8a6c-415f-a63d-11c80cf73f&&active==true&_sort==_id&search-offset==50&_count==50
//
//function GraphQLSearchWrapper.getPropertyValue(propName: string): Property;
//var
// list : List<GraphQLSearchEdge>;
// be : BundleEntry;
//{
// if (propName == "first")
// result = Property.Create(self, propname, "string", false, String, extractLink("first"))
// else if (propName == "previous")
// result = Property.Create(self, propname, "string", false, String, extractLink("previous"))
// else if (propName == "next")
// result = Property.Create(self, propname, "string", false, String, extractLink("next"))
// else if (propName == "last")
// result = Property.Create(self, propname, "string", false, String, extractLink("last"))
// else if (propName == "count")
// result = Property.Create(self, propname, "integer", false, String, FBundle.totalElement)
// else if (propName == "offset")
// result = Property.Create(self, propname, "integer", false, Integer, extractParam("search-offset", true))
// else if (propName == "pagesize")
// result = Property.Create(self, propname, "integer", false, Integer, extractParam("_count", true))
// else if (propName == "edges")
// {
// list = ArrayList<GraphQLSearchEdge>();
// try
// for be in FBundle.getEntry() do
// list.add(GraphQLSearchEdge.create(be));
// result = Property.Create(self, propname, "integer", true, Integer, List<Base>(list));
// finally
// list.Free;
// }
// }
// else
// result = null;
//}
//
//private void GraphQLSearchWrapper.SetBundle(const Value: Bundle);
//{
// FBundle.Free;
// FBundle = Value;
//}
//
//{ GraphQLSearchEdge }
//
//constructor GraphQLSearchEdge.Create(entry: BundleEntry);
//{
// inherited Create;
// FEntry = entry;
//}
//
//destructor GraphQLSearchEdge.Destroy;
//{
// FEntry.Free;
// inherited;
//}
//
//function GraphQLSearchEdge.fhirType(): String;
//{
// result = "*Edge";
//}
//
//function GraphQLSearchEdge.getPropertyValue(propName: string): Property;
//{
// if (propName == "mode")
// {
// if (FEntry.search != null)
// result = Property.Create(self, propname, "code", false, Enum, FEntry.search.modeElement)
// else
// result = Property.Create(self, propname, "code", false, Enum, Base(null));
// }
// else if (propName == "score")
// {
// if (FEntry.search != null)
// result = Property.Create(self, propname, "decimal", false, Decimal, FEntry.search.scoreElement)
// else
// result = Property.Create(self, propname, "decimal", false, Decimal, Base(null));
// }
// else if (propName == "resource")
// result = Property.Create(self, propname, "resource", false, Resource, FEntry.getResource())
// else
// result = null;
//}
//
//private void GraphQLSearchEdge.SetEntry(const Value: BundleEntry);
//{
// FEntry.Free;
// FEntry = value;
//}
//
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -21,6 +21,18 @@ package org.hl7.fhir.r4.utils;
*/ */
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.*;
import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus;
import org.hl7.fhir.utilities.graphql.Package;
import org.hl7.fhir.utilities.graphql.Operation.OperationType;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,239 +40,41 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException; import static org.hl7.fhir.utilities.graphql.IGraphQLStorageServices.ReferenceResolution;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.model.BackboneElement;
import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Element;
import org.hl7.fhir.r4.model.ExpressionNode;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Property;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.utils.GraphQLEngine.IGraphQLStorageServices.ReferenceResolution;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus;
import org.hl7.fhir.utilities.graphql.Directive;
import org.hl7.fhir.utilities.graphql.EGraphEngine;
import org.hl7.fhir.utilities.graphql.EGraphQLException;
import org.hl7.fhir.utilities.graphql.Field;
import org.hl7.fhir.utilities.graphql.Fragment;
import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.NumberValue;
import org.hl7.fhir.utilities.graphql.ObjectValue;
import org.hl7.fhir.utilities.graphql.Operation;
import org.hl7.fhir.utilities.graphql.Operation.OperationType;
import org.hl7.fhir.utilities.graphql.Package;
import org.hl7.fhir.utilities.graphql.Selection;
import org.hl7.fhir.utilities.graphql.StringValue;
import org.hl7.fhir.utilities.graphql.Value;
import org.hl7.fhir.utilities.graphql.Variable;
import org.hl7.fhir.utilities.graphql.VariableValue;
public class GraphQLEngine { public class GraphQLEngine implements IGraphQLEngine {
public class SearchEdge extends Base {
private BundleEntryComponent be;
private String type;
public SearchEdge(String type, BundleEntryComponent be) {
this.type = type;
this.be = be;
}
@Override
public String fhirType() {
return type;
}
@Override
protected void listChildren(List<Property> result) {
throw new Error("Not Implemented");
}
@Override
public String getIdBase() {
throw new Error("Not Implemented");
}
@Override
public void setIdBase(String value) {
throw new Error("Not Implemented");
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 3357091: /*mode*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasMode() ? be.getSearch().getModeElement() : null);
case 109264530: /*score*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasScore() ? be.getSearch().getScoreElement() : null);
case -341064690: /*resource*/ return new Property(_name, "resource", "n/a", 0, 1, be.hasResource() ? be.getResource() : null);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
}
public class SearchWrapper extends Base {
private Bundle bnd;
private String type;
private Map<String, String> map;
public SearchWrapper(String type, Bundle bnd) throws FHIRException {
this.type = type;
this.bnd = bnd;
for (BundleLinkComponent bl : bnd.getLink())
if (bl.getRelation().equals("self"))
map = parseURL(bl.getUrl());
}
@Override
public String fhirType() {
return type;
}
@Override
protected void listChildren(List<Property> result) {
throw new Error("Not Implemented");
}
@Override
public String getIdBase() {
throw new Error("Not Implemented");
}
@Override
public void setIdBase(String value) {
throw new Error("Not Implemented");
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 97440432: /*first*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case -1273775369: /*previous*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 3377907: /*next*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 3314326: /*last*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 94851343: /*count*/ return new Property(_name, "integer", "n/a", 0, 1, bnd.getTotalElement());
case -1019779949:/*offset*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("search-offset"));
case 860381968: /*pagesize*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("_count"));
case 96356950: /*edges*/ return new Property(_name, "edge", "n/a", 0, Integer.MAX_VALUE, getEdges());
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
private List<Base> getEdges() {
List<Base> list = new ArrayList<>();
for (BundleEntryComponent be : bnd.getEntry())
list.add(new SearchEdge(type.substring(0, type.length()-10)+"Edge", be));
return list;
}
private Base extractParam(String name) throws FHIRException {
return map != null ? new IntegerType(map.get(name)) : null;
}
private Map<String, String> parseURL(String url) throws FHIRException {
try {
Map<String, String> map = new HashMap<String, String>();
String[] pairs = url.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key;
key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
map.put(key, value);
}
return map;
} catch (UnsupportedEncodingException e) {
throw new FHIRException(e);
}
}
private Base extractLink(String _name) throws FHIRException {
for (BundleLinkComponent bl : bnd.getLink()) {
if (bl.getRelation().equals(_name)) {
Map<String, String> map = parseURL(bl.getUrl());
return new StringType(map.get("search-id")+':'+map.get("search-offset"));
}
}
return null;
}
}
public interface IGraphQLStorageServices {
public class ReferenceResolution {
private Resource targetContext;
private Resource target;
public ReferenceResolution(Resource targetContext, Resource target) {
super();
this.targetContext = targetContext;
this.target = target;
}
}
// given a reference inside a context, return what it references (including resolving internal references (e.g. start with #)
public ReferenceResolution lookup(Object appInfo, Resource context, Reference reference) throws FHIRException;
// just get the identified resource
public Resource lookup(Object appInfo, String type, String id) throws FHIRException;
// list the matching resources. searchParams are the standard search params.
// this instanceof different to search because the server returns all matching resources, or an error. There instanceof no paging on this search
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<Resource> matches) throws FHIRException;
// just perform a standard search, and return the bundle as you return to the client
public Bundle search(Object appInfo, String type, List<Argument> searchParams) throws FHIRException;
}
private IWorkerContext context; private IWorkerContext context;
/**
* for the host to pass context into and get back on the reference resolution interface
*/
private Object appInfo;
/**
* the focus resource - if (there instanceof one. if (there isn"t,) there instanceof no focus
*/
private Resource focus;
/**
* The package that describes the graphQL to be executed, operation name, and variables
*/
private Package graphQL;
/**
* where the output from executing the query instanceof going to go
*/
private ObjectValue output;
/**
* Application provided reference resolution services
*/
private IGraphQLStorageServices services;
// internal stuff
private Map<String, Argument> workingVariables = new HashMap<String, Argument>();
private FHIRPathEngine fpe;
private ExpressionNode magicExpression;
public GraphQLEngine(IWorkerContext context) { public GraphQLEngine(IWorkerContext context) {
super(); super();
this.context = context; this.context = context;
} }
/**
* for the host to pass context into and get back on the reference resolution interface
*/
private Object appInfo;
/**
* the focus resource - if (there instanceof one. if (there isn"t,) there instanceof no focus
*/
private Resource focus;
/**
* The package that describes the graphQL to be executed, operation name, and variables
*/
private Package graphQL;
/**
* where the output from executing the query instanceof going to go
*/
private ObjectValue output;
/**
* Application provided reference resolution services
*/
private IGraphQLStorageServices services;
// internal stuff
private Map<String, Argument> workingVariables = new HashMap<String, Argument>();
private FHIRPathEngine fpe;
private ExpressionNode magicExpression;
public void execute() throws EGraphEngine, EGraphQLException, FHIRException { public void execute() throws EGraphEngine, EGraphQLException, FHIRException {
if (graphQL == null) if (graphQL == null)
throw new EGraphEngine("Unable to process graphql - graphql document missing"); throw new EGraphEngine("Unable to process graphql - graphql document missing");
@ -314,8 +128,7 @@ public class GraphQLEngine {
include = dir; include = dir;
else else
throw new EGraphQLException("Duplicate @include directives"); throw new EGraphQLException("Duplicate @include directives");
} } else if (!Utilities.existsInList(dir.getName(), "flatten", "first", "singleton", "slice"))
else if (!Utilities.existsInList(dir.getName(), "flatten", "first", "singleton", "slice"))
throw new EGraphQLException("Directive \"" + dir.getName() + "\" instanceof not recognised"); throw new EGraphQLException("Directive \"" + dir.getName() + "\" instanceof not recognised");
} }
if ((skip != null && include != null)) if ((skip != null && include != null))
@ -332,7 +145,7 @@ public class GraphQLEngine {
} }
private boolean targetTypeOk(List<Argument> arguments, Resource dest) throws EGraphQLException { private boolean targetTypeOk(List<Argument> arguments, IBaseResource dest) throws EGraphQLException {
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
for (Argument arg : arguments) { for (Argument arg : arguments) {
if ((arg.getName().equals("type"))) { if ((arg.getName().equals("type"))) {
@ -403,7 +216,8 @@ public class GraphQLEngine {
break; break;
} }
i++; i++;
} else { }
else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.toString().substring(5));
for (Base v : values) { for (Base v : values) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
@ -436,18 +250,18 @@ public class GraphQLEngine {
return result; return result;
} }
private List<Resource> filterResources(Argument fhirpath, List<Resource> list) throws EGraphQLException, FHIRException { private List<Resource> filterResources(Argument fhirpath, List<IBaseResource> list) throws EGraphQLException, FHIRException {
List<Resource> result = new ArrayList<Resource>(); List<Resource> result = new ArrayList<Resource>();
if (list.size() > 0) { if (list.size() > 0) {
if ((fhirpath == null)) if ((fhirpath == null))
for (Resource v : list) for (IBaseResource v : list)
result.add(v); result.add((Resource) v);
else { else {
FHIRPathEngine fpe = new FHIRPathEngine(context); FHIRPathEngine fpe = new FHIRPathEngine(context);
ExpressionNode node = fpe.parse(getSingleValue(fhirpath)); ExpressionNode node = fpe.parse(getSingleValue(fhirpath));
for (Resource v : list) for (IBaseResource v : list)
if (fpe.evaluateToBoolean(null, v, v, node)) if (fpe.evaluateToBoolean(null, (Resource) v, (Base) v, node))
result.add(v); result.add((Resource) v);
} }
} }
return result; return result;
@ -609,14 +423,13 @@ public class GraphQLEngine {
Reference ref = (Reference) source; Reference ref = (Reference) source;
ReferenceResolution res = services.lookup(appInfo, context, ref); ReferenceResolution res = services.lookup(appInfo, context, ref);
if (res != null) { if (res != null) {
if (targetTypeOk(field.getArguments(), res.target)) { if (targetTypeOk(field.getArguments(), res.getTarget())) {
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList)); Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
arg.addValue(obj); arg.addValue(obj);
processObject(res.targetContext, res.target, obj, field.getSelectionSet(), inheritedList, suffix); processObject((Resource) res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix);
} }
} } else if (!hasArgument(field.getArguments(), "optional", "true"))
else if (!hasArgument(field.getArguments(), "optional", "true"))
throw new EGraphQLException("Unable to resolve reference to " + ref.getReference()); throw new EGraphQLException("Unable to resolve reference to " + ref.getReference());
} }
@ -629,14 +442,13 @@ public class GraphQLEngine {
Reference ref = new Reference(source.primitiveValue()); Reference ref = new Reference(source.primitiveValue());
ReferenceResolution res = services.lookup(appInfo, context, ref); ReferenceResolution res = services.lookup(appInfo, context, ref);
if (res != null) { if (res != null) {
if (targetTypeOk(field.getArguments(), res.target)) { if (targetTypeOk(field.getArguments(), res.getTarget())) {
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList)); Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
arg.addValue(obj); arg.addValue(obj);
processObject(res.targetContext, res.target, obj, field.getSelectionSet(), inheritedList, suffix); processObject((Resource) res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix);
} }
} } else if (!hasArgument(field.getArguments(), "optional", "true"))
else if (!hasArgument(field.getArguments(), "optional", "true"))
throw new EGraphQLException("Unable to resolve reference to " + ref.getReference()); throw new EGraphQLException("Unable to resolve reference to " + ref.getReference());
} }
@ -652,7 +464,7 @@ public class GraphQLEngine {
private void processReverseReferenceList(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { private void processReverseReferenceList(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null) if (services == null)
throw new EGraphQLException("Resource Referencing services not provided"); throw new EGraphQLException("Resource Referencing services not provided");
List<Resource> list = new ArrayList<Resource>(); List<IBaseResource> list = new ArrayList<>();
List<Argument> params = new ArrayList<Argument>(); List<Argument> params = new ArrayList<Argument>();
Argument parg = null; Argument parg = null;
for (Argument a : field.getArguments()) for (Argument a : field.getArguments())
@ -701,7 +513,7 @@ public class GraphQLEngine {
params.add(arg); params.add(arg);
arg.setName(getSingleValue(parg)); arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType() + "/" + source.getId())); arg.addValue(new StringValue(source.fhirType() + "/" + source.getId()));
Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length() - 10), params);
Base bndWrapper = new SearchWrapper(field.getName(), bnd); Base bndWrapper = new SearchWrapper(field.getName(), bnd);
arg = target.addField(field.getAlias() + suffix, listStatus(field, false)); arg = target.addField(field.getAlias() + suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
@ -735,7 +547,7 @@ public class GraphQLEngine {
throw new EGraphQLException("Unknown/invalid parameter " + arg.getName()); throw new EGraphQLException("Unknown/invalid parameter " + arg.getName());
if (Utilities.noString(id)) if (Utilities.noString(id))
throw new EGraphQLException("No id found"); throw new EGraphQLException("No id found");
Resource res = services.lookup(appInfo, field.getName(), id); Resource res = (Resource) services.lookup(appInfo, field.getName(), id);
if (res == null) if (res == null)
throw new EGraphQLException("Resource " + field.getName() + "/" + id + " not found"); throw new EGraphQLException("Resource " + field.getName() + "/" + id + " not found");
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, false)); Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, false));
@ -747,7 +559,7 @@ public class GraphQLEngine {
private void processSearchSimple(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { private void processSearchSimple(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null) if (services == null)
throw new EGraphQLException("Resource Referencing services not provided"); throw new EGraphQLException("Resource Referencing services not provided");
List<Resource> list = new ArrayList<Resource>(); List<IBaseResource> list = new ArrayList<>();
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list); services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list);
Argument arg = null; Argument arg = null;
ObjectValue obj = null; ObjectValue obj = null;
@ -774,13 +586,14 @@ public class GraphQLEngine {
else else
params.add(arg); params.add(arg);
if ((carg != null)) { if ((carg != null)) {
params.clear();; params.clear();
;
String[] parts = getSingleValue(carg).split(":"); String[] parts = getSingleValue(carg).split(":");
params.add(new Argument("search-id", new StringValue(parts[0]))); params.add(new Argument("search-id", new StringValue(parts[0])));
params.add(new Argument("search-offset", new StringValue(parts[1]))); params.add(new Argument("search-offset", new StringValue(parts[1])));
} }
Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length() - 10), params);
SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd); SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd);
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, false)); Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
@ -823,9 +636,6 @@ public class GraphQLEngine {
return result; return result;
} }
public Object getAppInfo() { public Object getAppInfo() {
return appInfo; return appInfo;
} }
@ -838,14 +648,16 @@ public class GraphQLEngine {
return focus; return focus;
} }
public void setFocus(Resource focus) { @Override
this.focus = focus; public void setFocus(IBaseResource focus) {
this.focus = (Resource) focus;
} }
public Package getGraphQL() { public Package getGraphQL() {
return graphQL; return graphQL;
} }
@Override
public void setGraphQL(Package graphQL) { public void setGraphQL(Package graphQL) {
this.graphQL = graphQL; this.graphQL = graphQL;
} }
@ -858,10 +670,154 @@ public class GraphQLEngine {
return services; return services;
} }
@Override
public void setServices(IGraphQLStorageServices services) { public void setServices(IGraphQLStorageServices services) {
this.services = services; this.services = services;
} }
public static class SearchEdge extends Base {
private BundleEntryComponent be;
private String type;
SearchEdge(String type, BundleEntryComponent be) {
this.type = type;
this.be = be;
}
@Override
public String fhirType() {
return type;
}
@Override
protected void listChildren(List<Property> result) {
throw new Error("Not Implemented");
}
@Override
public String getIdBase() {
throw new Error("Not Implemented");
}
@Override
public void setIdBase(String value) {
throw new Error("Not Implemented");
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 3357091: /*mode*/
return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasMode() ? be.getSearch().getModeElement() : null);
case 109264530: /*score*/
return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasScore() ? be.getSearch().getScoreElement() : null);
case -341064690: /*resource*/
return new Property(_name, "resource", "n/a", 0, 1, be.hasResource() ? be.getResource() : null);
default:
return super.getNamedProperty(_hash, _name, _checkValid);
}
}
}
public static class SearchWrapper extends Base {
private Bundle bnd;
private String type;
private Map<String, String> map;
SearchWrapper(String type, Bundle bnd) throws FHIRException {
this.type = type;
this.bnd = bnd;
for (BundleLinkComponent bl : bnd.getLink())
if (bl.getRelation().equals("self"))
map = parseURL(bl.getUrl());
}
@Override
public String fhirType() {
return type;
}
@Override
protected void listChildren(List<Property> result) {
throw new Error("Not Implemented");
}
@Override
public String getIdBase() {
throw new Error("Not Implemented");
}
@Override
public void setIdBase(String value) {
throw new Error("Not Implemented");
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 97440432: /*first*/
return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case -1273775369: /*previous*/
return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 3377907: /*next*/
return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 3314326: /*last*/
return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
case 94851343: /*count*/
return new Property(_name, "integer", "n/a", 0, 1, bnd.getTotalElement());
case -1019779949:/*offset*/
return new Property(_name, "integer", "n/a", 0, 1, extractParam("search-offset"));
case 860381968: /*pagesize*/
return new Property(_name, "integer", "n/a", 0, 1, extractParam("_count"));
case 96356950: /*edges*/
return new Property(_name, "edge", "n/a", 0, Integer.MAX_VALUE, getEdges());
default:
return super.getNamedProperty(_hash, _name, _checkValid);
}
}
private List<Base> getEdges() {
List<Base> list = new ArrayList<>();
for (BundleEntryComponent be : bnd.getEntry())
list.add(new SearchEdge(type.substring(0, type.length() - 10) + "Edge", be));
return list;
}
private Base extractParam(String name) throws FHIRException {
return map != null ? new IntegerType(map.get(name)) : null;
}
private Map<String, String> parseURL(String url) throws FHIRException {
try {
Map<String, String> map = new HashMap<String, String>();
String[] pairs = url.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key;
key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
map.put(key, value);
}
return map;
} catch (UnsupportedEncodingException e) {
throw new FHIRException(e);
}
}
private Base extractLink(String _name) throws FHIRException {
for (BundleLinkComponent bl : bnd.getLink()) {
if (bl.getRelation().equals(_name)) {
Map<String, String> map = parseURL(bl.getUrl());
return new StringType(map.get("search-id") + ':' + map.get("search-offset"));
}
}
return null;
}
}
// //
//{ GraphQLSearchWrapper } //{ GraphQLSearchWrapper }

View File

@ -1,32 +1,22 @@
package org.hl7.fhir.r4.test; package org.hl7.fhir.r4.test;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.context.SimpleWorkerContext; import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.formats.XmlParser; import org.hl7.fhir.r4.formats.XmlParser;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent; import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.r4.model.Bundle.SearchEntryMode; import org.hl7.fhir.r4.model.Bundle.SearchEntryMode;
import org.hl7.fhir.r4.test.utils.TestingUtilities;
import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.test.utils.TestingUtilities;
import org.hl7.fhir.r4.utils.GraphQLEngine; import org.hl7.fhir.r4.utils.GraphQLEngine;
import org.hl7.fhir.r4.utils.GraphQLEngine.IGraphQLStorageServices;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument; import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.NameValue; import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.Parser; import org.hl7.fhir.utilities.graphql.Parser;
import org.hl7.fhir.utilities.xml.XMLUtil; import org.hl7.fhir.utilities.xml.XMLUtil;
@ -38,6 +28,16 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class GraphQLEngineTests implements IGraphQLStorageServices { public class GraphQLEngineTests implements IGraphQLStorageServices {
@ -127,18 +127,18 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
} }
@Override @Override
public ReferenceResolution lookup(Object appInfo, Resource context, Reference reference) throws FHIRException { public ReferenceResolution lookup(Object appInfo, IBaseResource context, IBaseReference reference) throws FHIRException {
try { try {
if (reference.getReference().startsWith("#")) { if (reference.getReferenceElement().isLocal()) {
if (!(context instanceof DomainResource)) if (!(context instanceof DomainResource))
return null; return null;
for (Resource r : ((DomainResource)context).getContained()) { for (Resource r : ((DomainResource)context).getContained()) {
if (('#'+r.getId()).equals(reference.getReference())) { if (('#'+r.getId()).equals(reference.getReferenceElement().getValue())) {
return new ReferenceResolution(context, r); return new ReferenceResolution(context, r);
} }
} }
} else { } else {
String[] parts = reference.getReference().split("/"); String[] parts = reference.getReferenceElement().getValue().split("/");
String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase()+'-'+parts[1].toLowerCase()+".xml"); String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase()+'-'+parts[1].toLowerCase()+".xml");
if (new File(filename).exists()) if (new File(filename).exists())
return new ReferenceResolution(null, new XmlParser().parse(new FileInputStream(filename))); return new ReferenceResolution(null, new XmlParser().parse(new FileInputStream(filename)));
@ -150,7 +150,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
} }
@Override @Override
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<Resource> matches) throws FHIRException { public void listResources(Object appInfo, String type, List<Argument> searchParams, List<IBaseResource> matches) throws FHIRException {
try { try {
if (type.equals("Condition")) if (type.equals("Condition"))
matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml")))); matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml"))));

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -21,6 +21,18 @@ package org.hl7.fhir.r5.utils;
*/ */
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.*;
import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus;
import org.hl7.fhir.utilities.graphql.Package;
import org.hl7.fhir.utilities.graphql.Operation.OperationType;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,51 +40,16 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException; import static org.hl7.fhir.utilities.graphql.IGraphQLStorageServices.ReferenceResolution;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.BackboneElement;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Element;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.utils.GraphQLEngine.IGraphQLStorageServices.ReferenceResolution;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus;
import org.hl7.fhir.utilities.graphql.Directive;
import org.hl7.fhir.utilities.graphql.EGraphEngine;
import org.hl7.fhir.utilities.graphql.EGraphQLException;
import org.hl7.fhir.utilities.graphql.Field;
import org.hl7.fhir.utilities.graphql.Fragment;
import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.NumberValue;
import org.hl7.fhir.utilities.graphql.ObjectValue;
import org.hl7.fhir.utilities.graphql.Operation;
import org.hl7.fhir.utilities.graphql.Operation.OperationType;
import org.hl7.fhir.utilities.graphql.Package;
import org.hl7.fhir.utilities.graphql.Selection;
import org.hl7.fhir.utilities.graphql.StringValue;
import org.hl7.fhir.utilities.graphql.Value;
import org.hl7.fhir.utilities.graphql.Variable;
import org.hl7.fhir.utilities.graphql.VariableValue;
public class GraphQLEngine { public class GraphQLEngine implements IGraphQLEngine {
public class SearchEdge extends Base { public static class SearchEdge extends Base {
private BundleEntryComponent be; private BundleEntryComponent be;
private String type; private String type;
public SearchEdge(String type, BundleEntryComponent be) { SearchEdge(String type, BundleEntryComponent be) {
this.type = type; this.type = type;
this.be = be; this.be = be;
} }
@ -107,13 +84,13 @@ public class GraphQLEngine {
} }
} }
public class SearchWrapper extends Base { public static class SearchWrapper extends Base {
private Bundle bnd; private Bundle bnd;
private String type; private String type;
private Map<String, String> map; private Map<String, String> map;
public SearchWrapper(String type, Bundle bnd) throws FHIRException { SearchWrapper(String type, Bundle bnd) throws FHIRException {
this.type = type; this.type = type;
this.bnd = bnd; this.bnd = bnd;
for (BundleLinkComponent bl : bnd.getLink()) for (BundleLinkComponent bl : bnd.getLink())
@ -196,32 +173,6 @@ public class GraphQLEngine {
} }
public interface IGraphQLStorageServices {
public class ReferenceResolution {
private Resource targetContext;
private Resource target;
public ReferenceResolution(Resource targetContext, Resource target) {
super();
this.targetContext = targetContext;
this.target = target;
}
}
// given a reference inside a context, return what it references (including resolving internal references (e.g. start with #)
public ReferenceResolution lookup(Object appInfo, Resource context, Reference reference) throws FHIRException;
// just get the identified resource
public Resource lookup(Object appInfo, String type, String id) throws FHIRException;
// list the matching resources. searchParams are the standard search params.
// this instanceof different to search because the server returns all matching resources, or an error. There instanceof no paging on this search
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<Resource> matches) throws FHIRException;
// just perform a standard search, and return the bundle as you return to the client
public Bundle search(Object appInfo, String type, List<Argument> searchParams) throws FHIRException;
}
private IWorkerContext context; private IWorkerContext context;
public GraphQLEngine(IWorkerContext context) { public GraphQLEngine(IWorkerContext context) {
@ -261,6 +212,7 @@ public class GraphQLEngine {
private ExpressionNode magicExpression; private ExpressionNode magicExpression;
@Override
public void execute() throws EGraphEngine, EGraphQLException, FHIRException { public void execute() throws EGraphEngine, EGraphQLException, FHIRException {
if (graphQL == null) if (graphQL == null)
throw new EGraphEngine("Unable to process graphql - graphql document missing"); throw new EGraphEngine("Unable to process graphql - graphql document missing");
@ -332,7 +284,7 @@ public class GraphQLEngine {
} }
private boolean targetTypeOk(List<Argument> arguments, Resource dest) throws EGraphQLException { private boolean targetTypeOk(List<Argument> arguments, IBaseResource dest) throws EGraphQLException {
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
for (Argument arg : arguments) { for (Argument arg : arguments) {
if ((arg.getName().equals("type"))) { if ((arg.getName().equals("type"))) {
@ -436,18 +388,18 @@ public class GraphQLEngine {
return result; return result;
} }
private List<Resource> filterResources(Argument fhirpath, List<Resource> list) throws EGraphQLException, FHIRException { private List<Resource> filterResources(Argument fhirpath, List<IBaseResource> list) throws EGraphQLException, FHIRException {
List<Resource> result = new ArrayList<Resource>(); List<Resource> result = new ArrayList<Resource>();
if (list.size() > 0) { if (list.size() > 0) {
if ((fhirpath == null)) if ((fhirpath == null))
for (Resource v : list) for (IBaseResource v : list)
result.add(v); result.add((Resource) v);
else { else {
FHIRPathEngine fpe = new FHIRPathEngine(context); FHIRPathEngine fpe = new FHIRPathEngine(context);
ExpressionNode node = fpe.parse(getSingleValue(fhirpath)); ExpressionNode node = fpe.parse(getSingleValue(fhirpath));
for (Resource v : list) for (IBaseResource v : list)
if (fpe.evaluateToBoolean(null, v, v, node)) if (fpe.evaluateToBoolean(null, (Resource)v, (Base) v, node))
result.add(v); result.add((Resource) v);
} }
} }
return result; return result;
@ -609,11 +561,11 @@ public class GraphQLEngine {
Reference ref = (Reference) source; Reference ref = (Reference) source;
ReferenceResolution res = services.lookup(appInfo, context, ref); ReferenceResolution res = services.lookup(appInfo, context, ref);
if (res != null) { if (res != null) {
if (targetTypeOk(field.getArguments(), res.target)) { if (targetTypeOk(field.getArguments(), res.getTarget())) {
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList)); Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
arg.addValue(obj); arg.addValue(obj);
processObject(res.targetContext, res.target, obj, field.getSelectionSet(), inheritedList, suffix); processObject((Resource)res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix);
} }
} }
else if (!hasArgument(field.getArguments(), "optional", "true")) else if (!hasArgument(field.getArguments(), "optional", "true"))
@ -629,11 +581,11 @@ public class GraphQLEngine {
Reference ref = new Reference(source.primitiveValue()); Reference ref = new Reference(source.primitiveValue());
ReferenceResolution res = services.lookup(appInfo, context, ref); ReferenceResolution res = services.lookup(appInfo, context, ref);
if (res != null) { if (res != null) {
if (targetTypeOk(field.getArguments(), res.target)) { if (targetTypeOk(field.getArguments(), res.getTarget())) {
Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList)); Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
arg.addValue(obj); arg.addValue(obj);
processObject(res.targetContext, res.target, obj, field.getSelectionSet(), inheritedList, suffix); processObject((Resource)res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix);
} }
} }
else if (!hasArgument(field.getArguments(), "optional", "true")) else if (!hasArgument(field.getArguments(), "optional", "true"))
@ -652,8 +604,8 @@ public class GraphQLEngine {
private void processReverseReferenceList(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { private void processReverseReferenceList(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null) if (services == null)
throw new EGraphQLException("Resource Referencing services not provided"); throw new EGraphQLException("Resource Referencing services not provided");
List<Resource> list = new ArrayList<Resource>(); List<IBaseResource> list = new ArrayList<>();
List<Argument> params = new ArrayList<Argument>(); List<Argument> params = new ArrayList<>();
Argument parg = null; Argument parg = null;
for (Argument a : field.getArguments()) for (Argument a : field.getArguments())
if (!(a.getName().equals("_reference"))) if (!(a.getName().equals("_reference")))
@ -701,7 +653,7 @@ public class GraphQLEngine {
params.add(arg); params.add(arg);
arg.setName(getSingleValue(parg)); arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getId())); arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params);
Base bndWrapper = new SearchWrapper(field.getName(), bnd); Base bndWrapper = new SearchWrapper(field.getName(), bnd);
arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); arg = target.addField(field.getAlias()+suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
@ -735,7 +687,7 @@ public class GraphQLEngine {
throw new EGraphQLException("Unknown/invalid parameter "+arg.getName()); throw new EGraphQLException("Unknown/invalid parameter "+arg.getName());
if (Utilities.noString(id)) if (Utilities.noString(id))
throw new EGraphQLException("No id found"); throw new EGraphQLException("No id found");
Resource res = services.lookup(appInfo, field.getName(), id); Resource res = (Resource) services.lookup(appInfo, field.getName(), id);
if (res == null) if (res == null)
throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found"); throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found");
Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false));
@ -747,7 +699,7 @@ public class GraphQLEngine {
private void processSearchSimple(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { private void processSearchSimple(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException {
if (services == null) if (services == null)
throw new EGraphQLException("Resource Referencing services not provided"); throw new EGraphQLException("Resource Referencing services not provided");
List<Resource> list = new ArrayList<Resource>(); List<IBaseResource> list = new ArrayList<>();
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list); services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list);
Argument arg = null; Argument arg = null;
ObjectValue obj = null; ObjectValue obj = null;
@ -780,7 +732,7 @@ public class GraphQLEngine {
params.add(new Argument("search-offset", new StringValue(parts[1]))); params.add(new Argument("search-offset", new StringValue(parts[1])));
} }
Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params);
SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd); SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd);
Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false));
ObjectValue obj = new ObjectValue(); ObjectValue obj = new ObjectValue();
@ -830,6 +782,7 @@ public class GraphQLEngine {
return appInfo; return appInfo;
} }
@Override
public void setAppInfo(Object appInfo) { public void setAppInfo(Object appInfo) {
this.appInfo = appInfo; this.appInfo = appInfo;
} }
@ -838,18 +791,21 @@ public class GraphQLEngine {
return focus; return focus;
} }
public void setFocus(Resource focus) { @Override
this.focus = focus; public void setFocus(IBaseResource focus) {
this.focus = (Resource) focus;
} }
public Package getGraphQL() { public Package getGraphQL() {
return graphQL; return graphQL;
} }
@Override
public void setGraphQL(Package graphQL) { public void setGraphQL(Package graphQL) {
this.graphQL = graphQL; this.graphQL = graphQL;
} }
@Override
public ObjectValue getOutput() { public ObjectValue getOutput() {
return output; return output;
} }
@ -858,6 +814,7 @@ public class GraphQLEngine {
return services; return services;
} }
@Override
public void setServices(IGraphQLStorageServices services) { public void setServices(IGraphQLStorageServices services) {
this.services = services; this.services = services;
} }

View File

@ -1,32 +1,21 @@
package org.hl7.fhir.r5.test; package org.hl7.fhir.r5.test;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.Bundle.BundleLinkComponent; import org.hl7.fhir.r5.model.Bundle.BundleLinkComponent;
import org.hl7.fhir.r5.model.Bundle.SearchEntryMode; import org.hl7.fhir.r5.model.Bundle.SearchEntryMode;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.GraphQLEngine; import org.hl7.fhir.r5.utils.GraphQLEngine;
import org.hl7.fhir.r5.utils.GraphQLEngine.IGraphQLStorageServices;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument; import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.NameValue; import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.Parser; import org.hl7.fhir.utilities.graphql.Parser;
import org.hl7.fhir.utilities.xml.XMLUtil; import org.hl7.fhir.utilities.xml.XMLUtil;
@ -38,6 +27,16 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class GraphQLEngineTests implements IGraphQLStorageServices { public class GraphQLEngineTests implements IGraphQLStorageServices {
@ -127,18 +126,18 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
} }
@Override @Override
public ReferenceResolution lookup(Object appInfo, Resource context, Reference reference) throws FHIRException { public ReferenceResolution lookup(Object appInfo, IBaseResource context, IBaseReference reference) throws FHIRException {
try { try {
if (reference.getReference().startsWith("#")) { if (reference.getReferenceElement().isLocal()) {
if (!(context instanceof DomainResource)) if (!(context instanceof DomainResource))
return null; return null;
for (Resource r : ((DomainResource)context).getContained()) { for (Resource r : ((DomainResource)context).getContained()) {
if (('#'+r.getId()).equals(reference.getReference())) { if (('#'+r.getId()).equals(reference.getReferenceElement().getValue())) {
return new ReferenceResolution(context, r); return new ReferenceResolution(context, r);
} }
} }
} else { } else {
String[] parts = reference.getReference().split("/"); String[] parts = reference.getReferenceElement().getValue().split("/");
String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase()+'-'+parts[1].toLowerCase()+".xml"); String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase()+'-'+parts[1].toLowerCase()+".xml");
if (new File(filename).exists()) if (new File(filename).exists())
return new ReferenceResolution(null, new XmlParser().parse(new FileInputStream(filename))); return new ReferenceResolution(null, new XmlParser().parse(new FileInputStream(filename)));
@ -150,7 +149,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
} }
@Override @Override
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<Resource> matches) throws FHIRException { public void listResources(Object appInfo, String type, List<Argument> searchParams, List<IBaseResource> matches) throws FHIRException {
try { try {
if (type.equals("Condition")) if (type.equals("Condition"))
matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml")))); matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml"))));

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -0,0 +1,39 @@
package org.hl7.fhir.utilities.graphql;
/*-
* #%L
* org.hl7.fhir.utilities
* %%
* Copyright (C) 2014 - 2019 Health Level 7
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource;
public interface IGraphQLEngine {
void execute() throws EGraphEngine, EGraphQLException, FHIRException;
ObjectValue getOutput();
void setAppInfo(Object appInfo);
void setFocus(IBaseResource focus);
void setGraphQL(Package graphQL);
void setServices(IGraphQLStorageServices services);
}

View File

@ -0,0 +1,73 @@
package org.hl7.fhir.utilities.graphql;
/*-
* #%L
* org.hl7.fhir.utilities
* %%
* Copyright (C) 2014 - 2019 Health Level 7
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
public interface IGraphQLStorageServices {
/**
* given a reference inside a context, return what it references (including resolving internal references (e.g. start with #)
*/
ReferenceResolution lookup(Object appInfo, IBaseResource context, IBaseReference reference) throws FHIRException;
/**
* just get the identified resource
*/
IBaseResource lookup(Object appInfo, String type, String id) throws FHIRException;
/**
* list the matching resources. searchParams are the standard search params.
* this instanceof different to search because the server returns all matching resources, or an error. There instanceof no paging on this search
*/
void listResources(Object appInfo, String type, List<Argument> searchParams, List<IBaseResource> matches) throws FHIRException;
/**
* just perform a standard search, and return the bundle as you return to the client
*/
IBaseBundle search(Object appInfo, String type, List<Argument> searchParams) throws FHIRException;
class ReferenceResolution {
private IBaseResource targetContext;
private IBaseResource target;
public ReferenceResolution(IBaseResource targetContext, IBaseResource target) {
super();
this.targetContext = targetContext;
this.target = target;
}
public IBaseResource getTargetContext() {
return targetContext;
}
public IBaseResource getTarget() {
return target;
}
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -0,0 +1,22 @@
/*-
* #%L
* org.hl7.fhir.validation.cli
* %%
* Copyright (C) 2014 - 2019 Health Level 7
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class Marker {
// This class is just here to force a javadoc build in order to satisfy maven central
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>3.8.0-SNAPSHOT</version> <version>4.0.0</version>
</parent> </parent>
<!-- <!--
@ -13,10 +13,10 @@
each other. It is fine to bump the point version of this POM without affecting each other. It is fine to bump the point version of this POM without affecting
HAPI FHIR. HAPI FHIR.
--> -->
<version>3.8.24-SNAPSHOT</version> <version>4.0.1-SNAPSHOT</version>
<properties> <properties>
<hapi_fhir_version>4.0.0-SNAPSHOT</hapi_fhir_version> <hapi_fhir_version>4.0.0</hapi_fhir_version>
</properties> </properties>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>