From ecfd55c368b39ae9aac4a338b0d4151fe66d82a8 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 12 Aug 2019 09:07:52 -0400 Subject: [PATCH 1/6] Backport GraphQL support to DSTU3 --- .../org/hl7/fhir/dstu3/model/Property.java | 7 +- .../hl7/fhir/dstu3/utils/GraphQLEngine.java | 964 ++++++++++++++++++ .../org/hl7/fhir/r4/utils/GraphQLEngine.java | 580 +++++------ .../hl7/fhir/r4/test/GraphQLEngineTests.java | 40 +- .../org/hl7/fhir/r5/utils/GraphQLEngine.java | 133 +-- .../hl7/fhir/r5/test/GraphQLEngineTests.java | 39 +- .../utilities/graphql/IGraphQLEngine.java | 39 + .../graphql/IGraphQLStorageServices.java | 73 ++ 8 files changed, 1433 insertions(+), 442 deletions(-) create mode 100644 org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLEngine.java create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLStorageServices.java diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java index 2c3d49875..0fb226cc5 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java @@ -82,7 +82,8 @@ public class Property { this.definition = definition; this.minCardinality = minCardinality; this.maxCardinality = maxCardinality; - this.values.add(value); + if (value != null) + this.values.add(value); } /** @@ -156,6 +157,8 @@ public class Property { this.structure = structure; } + public boolean isList() { + return maxCardinality > 1; + } - } diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java new file mode 100644 index 000000000..cbc8b18d6 --- /dev/null +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java @@ -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 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 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 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 getEdges() { + List 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 parseURL(String url) throws FHIRException { + try { + Map map = new HashMap(); + 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 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 workingVariables = new HashMap(); + + 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 vl = resolveValues(dir.getArguments().get(0), 1); + return vl.get(0).toString().equals("true"); + } + + private boolean checkDirectives(List 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 directives) { + + } + + private boolean targetTypeOk(List arguments, IBaseResource dest) throws EGraphQLException { + List list = new ArrayList(); + for (Argument arg : arguments) { + if ((arg.getName().equals("type"))) { + List 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 filter(Resource context, Property prop, List arguments, List values, boolean extensionMode) throws FHIRException, EGraphQLException { + List result = new ArrayList(); + if (values.size() > 0) { + int count = Integer.MAX_VALUE; + int offset = 0; + StringBuilder fp = new StringBuilder(); + for (Argument arg : arguments) { + List 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 filterResources(Argument fhirpath, Bundle bnd) throws EGraphQLException, FHIRException { + List result = new ArrayList(); + 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 filterResources(Argument fhirpath, List list) throws EGraphQLException, FHIRException { + List 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 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 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, 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 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 list = new ArrayList<>(); + List 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 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 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())); + 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, 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 list = new ArrayList<>(); + services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list); + Argument arg = null; + ObjectValue obj = null; + + List 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 params = new ArrayList(); + 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 vl = resolveValues(arg, 1); + if (vl.size() == 0) + return ""; + return vl.get(0).toString(); + } + + private List resolveValues(Argument arg) throws EGraphQLException { + return resolveValues(arg, -1, ""); + } + + private List resolveValues(Argument arg, int max) throws EGraphQLException { + return resolveValues(arg, max, ""); + } + + private List resolveValues(Argument arg, int max, String vars) throws EGraphQLException { + List result = new ArrayList(); + 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 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; +// 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(); +// try +// for be in FBundle.getEntry() do +// list.add(GraphQLSearchEdge.create(be)); +// result = Property.Create(self, propname, "integer", true, Integer, List(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; +//} +// +} diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/GraphQLEngine.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/GraphQLEngine.java index 94556050c..68f73dee9 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/GraphQLEngine.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/GraphQLEngine.java @@ -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.net.URLDecoder; import java.util.ArrayList; @@ -28,239 +40,41 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.hl7.fhir.exceptions.FHIRException; -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; +import static org.hl7.fhir.utilities.graphql.IGraphQLStorageServices.ReferenceResolution; -public class GraphQLEngine { - - public class SearchEdge extends Base { +public class GraphQLEngine implements IGraphQLEngine { - 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 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 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 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 getEdges() { - List 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 parseURL(String url) throws FHIRException { - try { - Map map = new HashMap(); - 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 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 searchParams, List 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 searchParams) throws FHIRException; - } - 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 workingVariables = new HashMap(); + private FHIRPathEngine fpe; + private ExpressionNode magicExpression; + 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 workingVariables = new HashMap(); - - 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"); @@ -274,7 +88,7 @@ public class GraphQLEngine { if (!Utilities.noString(graphQL.getOperationName())) { op = graphQL.getDocument().operation(graphQL.getOperationName()); if (op == null) - throw new EGraphEngine("Unable to find operation \""+graphQL.getOperationName()+"\""); + throw new EGraphEngine("Unable to find operation \"" + graphQL.getOperationName() + "\""); } else if ((graphQL.getDocument().getOperations().size() == 1)) op = graphQL.getDocument().getOperations().get(0); else @@ -293,9 +107,9 @@ public class GraphQLEngine { 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\""); + 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\""); + throw new EGraphQLException("Unable to process @" + dir.getName() + ": expected a single argument \"if\""); List vl = resolveValues(dir.getArguments().get(0), 1); return vl.get(0).toString().equals("true"); } @@ -314,9 +128,8 @@ public class GraphQLEngine { 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"); + } 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"); @@ -332,7 +145,7 @@ public class GraphQLEngine { } - private boolean targetTypeOk(List arguments, Resource dest) throws EGraphQLException { + private boolean targetTypeOk(List arguments, IBaseResource dest) throws EGraphQLException { List list = new ArrayList(); for (Argument arg : arguments) { if ((arg.getName().equals("type"))) { @@ -350,12 +163,12 @@ public class GraphQLEngine { 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; + 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) { @@ -378,9 +191,9 @@ public class GraphQLEngine { 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()+")"); + 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()); + 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"))) @@ -388,8 +201,8 @@ public class GraphQLEngine { 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()+"'"); + 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; @@ -403,18 +216,19 @@ public class GraphQLEngine { 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++; - } } + 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; } @@ -436,18 +250,18 @@ public class GraphQLEngine { return result; } - private List filterResources(Argument fhirpath, List list) throws EGraphQLException, FHIRException { + private List filterResources(Argument fhirpath, List list) throws EGraphQLException, FHIRException { List result = new ArrayList(); if (list.size() > 0) { if ((fhirpath == null)) - for (Resource v : list) - result.add(v); + for (IBaseResource v : list) + result.add((Resource) v); else { FHIRPathEngine fpe = new FHIRPathEngine(context); ExpressionNode node = fpe.parse(getSingleValue(fhirpath)); - for (Resource v : list) - if (fpe.evaluateToBoolean(null, v, v, node)) - result.add(v); + for (IBaseResource v : list) + if (fpe.evaluateToBoolean(null, (Resource) v, (Base) v, node)) + result.add((Resource) v); } } return result; @@ -475,23 +289,23 @@ public class GraphQLEngine { 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) + if (expression != null) throw new FHIRException("You cannot mix @slice and @first"); - arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), inheritedList)); + 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)); + 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); + ss = suffix + '.' + Integer.toString(index); else ss = suffix+'.'+fpe.evaluateToString(null, null, null, value, expression); if (!sel.getField().hasDirective("flatten")) - arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), prop.isList() || inheritedList)); + arg = target.addField(sel.getField().getAlias() + suffix, listStatus(sel.getField(), prop.isList() || inheritedList)); } if (value.isPrimitive() && !extensionMode) { @@ -518,7 +332,7 @@ public class GraphQLEngine { private void processVariables(Operation op) throws EGraphQLException { for (Variable varRef : op.getVariables()) { Argument varDef = null; - for (Argument v : graphQL.getVariables()) + for (Argument v : graphQL.getVariables()) if (v.getName().equals(varRef.getName())) varDef = v; if (varDef != null) @@ -537,7 +351,7 @@ public class GraphQLEngine { private boolean isResourceName(String name, String suffix) { if (!name.endsWith(suffix)) return false; - name = name.substring(0, name.length()-suffix.length()); + name = name.substring(0, name.length() - suffix.length()); return context.getResourceNamesAsSet().contains(name); } @@ -560,10 +374,10 @@ public class GraphQLEngine { 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()); + 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()); + throw new EGraphQLException("Unknown property " + sel.getField().getName() + " on " + source.fhirType()); List vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); if (!vl.isEmpty()) @@ -574,13 +388,13 @@ public class GraphQLEngine { 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())) + 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()); + 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? @@ -609,15 +423,14 @@ public class GraphQLEngine { Reference ref = (Reference) source; ReferenceResolution res = services.lookup(appInfo, context, ref); 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)); ObjectValue obj = new ObjectValue(); 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")) - throw new EGraphQLException("Unable to resolve reference to "+ref.getReference()); + } 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 { @@ -629,15 +442,14 @@ public class GraphQLEngine { Reference ref = new Reference(source.primitiveValue()); ReferenceResolution res = services.lookup(appInfo, context, ref); 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)); ObjectValue obj = new ObjectValue(); 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")) - throw new EGraphQLException("Unable to resolve reference to "+ref.getReference()); + } else if (!hasArgument(field.getArguments(), "optional", "true")) + throw new EGraphQLException("Unable to resolve reference to " + ref.getReference()); } private ArgumentListStatus listStatus(Field field, boolean isList) { @@ -652,7 +464,7 @@ public class GraphQLEngine { 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 list = new ArrayList(); + List list = new ArrayList<>(); List params = new ArrayList(); Argument parg = null; for (Argument a : field.getArguments()) @@ -667,14 +479,14 @@ public class GraphQLEngine { Argument arg = new Argument(); params.add(arg); arg.setName(getSingleValue(parg)); - arg.addValue(new StringValue(source.fhirType()+"/"+source.getId())); + 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 vl = filterResources(field.argument("fhirpath"), list); if (!vl.isEmpty()) { - arg = target.addField(field.getAlias()+suffix, listStatus(field, true)); + arg = target.addField(field.getAlias() + suffix, listStatus(field, true)); for (Resource v : vl) { obj = new ObjectValue(); arg.addValue(obj); @@ -682,7 +494,7 @@ public class GraphQLEngine { } } } - + 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"); @@ -700,10 +512,10 @@ public class GraphQLEngine { Argument arg = new Argument(); params.add(arg); arg.setName(getSingleValue(parg)); - arg.addValue(new StringValue(source.fhirType()+"/"+source.getId())); - Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); + 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)); + arg = target.addField(field.getAlias() + suffix, listStatus(field, false)); ObjectValue obj = new ObjectValue(); arg.addValue(obj); processObject(null, bndWrapper, obj, field.getSelectionSet(), inheritedList, suffix); @@ -732,13 +544,13 @@ public class GraphQLEngine { if ((arg.getName().equals("id"))) id = getSingleValue(arg); else - throw new EGraphQLException("Unknown/invalid parameter "+arg.getName()); + throw new EGraphQLException("Unknown/invalid parameter " + arg.getName()); if (Utilities.noString(id)) 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) - throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found"); - Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); + 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); @@ -747,14 +559,14 @@ public class GraphQLEngine { 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 list = new ArrayList(); + List list = new ArrayList<>(); services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list); Argument arg = null; ObjectValue obj = null; List vl = filterResources(field.argument("fhirpath"), list); if (!vl.isEmpty()) { - arg = target.addField(field.getAlias()+suffix, listStatus(field, true)); + arg = target.addField(field.getAlias() + suffix, listStatus(field, true)); for (Resource v : vl) { obj = new ObjectValue(); arg.addValue(obj); @@ -762,27 +574,28 @@ public class GraphQLEngine { } } } - + 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 params = new ArrayList(); Argument carg = null; - for ( Argument arg : field.getArguments()) + for (Argument arg : field.getArguments()) if (arg.getName().equals("cursor")) carg = arg; else params.add(arg); if ((carg != null)) { - params.clear();; + 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 = 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); - Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); + 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); @@ -798,34 +611,31 @@ public class GraphQLEngine { private List resolveValues(Argument arg) throws EGraphQLException { return resolveValues(arg, -1, ""); } - + private List resolveValues(Argument arg, int max) throws EGraphQLException { return resolveValues(arg, max, ""); } - + private List resolveValues(Argument arg, int max, String vars) throws EGraphQLException { List result = new ArrayList(); for (Value v : arg.getValues()) { - if (! (v instanceof VariableValue)) + if (!(v instanceof VariableValue)) result.add(v); else { - if (vars.contains(":"+v.toString()+":")) - throw new EGraphQLException("Recursive reference to variable "+v.toString()); + 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 vl = resolveValues(a, -1, vars+":"+v.toString()+":"); + throw new EGraphQLException("No value found for variable \"" + v.toString() + "\" in \"" + arg.getName() + "\""); + List 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"); + 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; } @@ -838,14 +648,16 @@ public class GraphQLEngine { return focus; } - public void setFocus(Resource focus) { - this.focus = 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; } @@ -858,10 +670,154 @@ public class GraphQLEngine { return services; } + @Override public void setServices(IGraphQLStorageServices 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 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 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 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 getEdges() { + List 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 parseURL(String url) throws FHIRException { + try { + Map map = new HashMap(); + 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 map = parseURL(bl.getUrl()); + return new StringType(map.get("search-id") + ':' + map.get("search-offset")); + } + } + return null; + } + + } + // //{ GraphQLSearchWrapper } diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/GraphQLEngineTests.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/GraphQLEngineTests.java index 73f42124b..30fde8d67 100644 --- a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/GraphQLEngineTests.java +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/GraphQLEngineTests.java @@ -1,32 +1,22 @@ 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.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.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleLinkComponent; 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.Reference; 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.IGraphQLStorageServices; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; 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.Parser; import org.hl7.fhir.utilities.xml.XMLUtil; @@ -38,6 +28,16 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; 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) public class GraphQLEngineTests implements IGraphQLStorageServices { @@ -127,18 +127,18 @@ public class GraphQLEngineTests implements IGraphQLStorageServices { } @Override - public ReferenceResolution lookup(Object appInfo, Resource context, Reference reference) throws FHIRException { + public ReferenceResolution lookup(Object appInfo, IBaseResource context, IBaseReference reference) throws FHIRException { try { - if (reference.getReference().startsWith("#")) { - if (!(context instanceof DomainResource)) + if (reference.getReferenceElement().isLocal()) { + if (!(context instanceof DomainResource)) return null; for (Resource r : ((DomainResource)context).getContained()) { - if (('#'+r.getId()).equals(reference.getReference())) { + if (('#'+r.getId()).equals(reference.getReferenceElement().getValue())) { return new ReferenceResolution(context, r); } } } else { - String[] parts = reference.getReference().split("/"); + String[] parts = reference.getReferenceElement().getValue().split("/"); String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase()+'-'+parts[1].toLowerCase()+".xml"); if (new File(filename).exists()) return new ReferenceResolution(null, new XmlParser().parse(new FileInputStream(filename))); @@ -150,7 +150,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices { } @Override - public void listResources(Object appInfo, String type, List searchParams, List matches) throws FHIRException { + public void listResources(Object appInfo, String type, List searchParams, List matches) throws FHIRException { try { if (type.equals("Condition")) matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml")))); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java index b58cb1dee..42c36d2f2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLEngine.java @@ -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.net.URLDecoder; import java.util.ArrayList; @@ -28,51 +40,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.hl7.fhir.exceptions.FHIRException; -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; +import static org.hl7.fhir.utilities.graphql.IGraphQLStorageServices.ReferenceResolution; -public class GraphQLEngine { +public class GraphQLEngine implements IGraphQLEngine { - public class SearchEdge extends Base { + public static class SearchEdge extends Base { private BundleEntryComponent be; private String type; - public SearchEdge(String type, BundleEntryComponent be) { + SearchEdge(String type, BundleEntryComponent be) { this.type = type; 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 String type; private Map map; - public SearchWrapper(String type, Bundle bnd) throws FHIRException { + SearchWrapper(String type, Bundle bnd) throws FHIRException { this.type = type; this.bnd = bnd; for (BundleLinkComponent bl : bnd.getLink()) @@ -159,7 +136,7 @@ public class GraphQLEngine { private List getEdges() { List list = new ArrayList<>(); for (BundleEntryComponent be : bnd.getEntry()) - list.add(new SearchEdge(type.substring(0, type.length()-10)+"Edge", be)); + list.add(new SearchEdge(type.substring(0, type.length() - 10) + "Edge", be)); return list; } @@ -195,32 +172,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 searchParams, List 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 searchParams) throws FHIRException; - } private IWorkerContext context; @@ -260,7 +211,8 @@ public class GraphQLEngine { private FHIRPathEngine fpe; private ExpressionNode magicExpression; - + + @Override public void execute() throws EGraphEngine, EGraphQLException, FHIRException { if (graphQL == null) throw new EGraphEngine("Unable to process graphql - graphql document missing"); @@ -332,7 +284,7 @@ public class GraphQLEngine { } - private boolean targetTypeOk(List arguments, Resource dest) throws EGraphQLException { + private boolean targetTypeOk(List arguments, IBaseResource dest) throws EGraphQLException { List list = new ArrayList(); for (Argument arg : arguments) { if ((arg.getName().equals("type"))) { @@ -436,18 +388,18 @@ public class GraphQLEngine { return result; } - private List filterResources(Argument fhirpath, List list) throws EGraphQLException, FHIRException { + private List filterResources(Argument fhirpath, List list) throws EGraphQLException, FHIRException { List result = new ArrayList(); if (list.size() > 0) { if ((fhirpath == null)) - for (Resource v : list) - result.add(v); + for (IBaseResource v : list) + result.add((Resource) v); else { FHIRPathEngine fpe = new FHIRPathEngine(context); ExpressionNode node = fpe.parse(getSingleValue(fhirpath)); - for (Resource v : list) - if (fpe.evaluateToBoolean(null, v, v, node)) - result.add(v); + for (IBaseResource v : list) + if (fpe.evaluateToBoolean(null, (Resource)v, (Base) v, node)) + result.add((Resource) v); } } return result; @@ -609,11 +561,11 @@ public class GraphQLEngine { Reference ref = (Reference) source; ReferenceResolution res = services.lookup(appInfo, context, ref); 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)); ObjectValue obj = new ObjectValue(); 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")) @@ -629,11 +581,11 @@ public class GraphQLEngine { Reference ref = new Reference(source.primitiveValue()); ReferenceResolution res = services.lookup(appInfo, context, ref); 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)); ObjectValue obj = new ObjectValue(); 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")) @@ -652,8 +604,8 @@ public class GraphQLEngine { 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 list = new ArrayList(); - List params = new ArrayList(); + List list = new ArrayList<>(); + List params = new ArrayList<>(); Argument parg = null; for (Argument a : field.getArguments()) if (!(a.getName().equals("_reference"))) @@ -701,7 +653,7 @@ public class GraphQLEngine { params.add(arg); arg.setName(getSingleValue(parg)); 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); arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); ObjectValue obj = new ObjectValue(); @@ -735,7 +687,7 @@ public class GraphQLEngine { throw new EGraphQLException("Unknown/invalid parameter "+arg.getName()); if (Utilities.noString(id)) 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) throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found"); 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 { if (services == null) throw new EGraphQLException("Resource Referencing services not provided"); - List list = new ArrayList(); + List list = new ArrayList<>(); services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list); Argument arg = null; ObjectValue obj = null; @@ -780,7 +732,7 @@ public class GraphQLEngine { 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); Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); ObjectValue obj = new ObjectValue(); @@ -830,6 +782,7 @@ public class GraphQLEngine { return appInfo; } + @Override public void setAppInfo(Object appInfo) { this.appInfo = appInfo; } @@ -838,18 +791,21 @@ public class GraphQLEngine { return focus; } - public void setFocus(Resource focus) { - this.focus = 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; } + @Override public ObjectValue getOutput() { return output; } @@ -858,6 +814,7 @@ public class GraphQLEngine { return services; } + @Override public void setServices(IGraphQLStorageServices services) { this.services = services; } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java index 35136b21c..bf045ab80 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java @@ -1,32 +1,21 @@ 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.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.model.Bundle; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.Bundle.BundleLinkComponent; 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.Reference; 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.IGraphQLStorageServices; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; 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.Parser; import org.hl7.fhir.utilities.xml.XMLUtil; @@ -38,6 +27,16 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; 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) public class GraphQLEngineTests implements IGraphQLStorageServices { @@ -127,18 +126,18 @@ public class GraphQLEngineTests implements IGraphQLStorageServices { } @Override - public ReferenceResolution lookup(Object appInfo, Resource context, Reference reference) throws FHIRException { + public ReferenceResolution lookup(Object appInfo, IBaseResource context, IBaseReference reference) throws FHIRException { try { - if (reference.getReference().startsWith("#")) { + if (reference.getReferenceElement().isLocal()) { if (!(context instanceof DomainResource)) return null; for (Resource r : ((DomainResource)context).getContained()) { - if (('#'+r.getId()).equals(reference.getReference())) { + if (('#'+r.getId()).equals(reference.getReferenceElement().getValue())) { return new ReferenceResolution(context, r); } } } else { - String[] parts = reference.getReference().split("/"); + String[] parts = reference.getReferenceElement().getValue().split("/"); String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase()+'-'+parts[1].toLowerCase()+".xml"); if (new File(filename).exists()) return new ReferenceResolution(null, new XmlParser().parse(new FileInputStream(filename))); @@ -150,7 +149,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices { } @Override - public void listResources(Object appInfo, String type, List searchParams, List matches) throws FHIRException { + public void listResources(Object appInfo, String type, List searchParams, List matches) throws FHIRException { try { if (type.equals("Condition")) matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml")))); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLEngine.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLEngine.java new file mode 100644 index 000000000..83a0f24cd --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLEngine.java @@ -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); +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLStorageServices.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLStorageServices.java new file mode 100644 index 000000000..9f11a6bfa --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/graphql/IGraphQLStorageServices.java @@ -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 searchParams, List 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 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; + } + + + } +} From 90ea715f29cc21dd3a93c1882d5d37ef965ed1bc Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 14 Aug 2019 11:12:41 -0400 Subject: [PATCH 2/6] Bump version to 4.0.0 for release --- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation.cli/src/main/java/Marker.java | 0 org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 4 ++-- 11 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 org.hl7.fhir.validation.cli/src/main/java/Marker.java diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 0dc1053fc..30efdbe82 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 24b7a38a0..ce2381b68 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index ac0e104b5..f9deee9e1 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 6008ed377..80856e53a 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index ea7837ab7..e724209c0 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 9f6bc3425..3ef497658 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index 8768c4516..47048e172 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 904d846c5..05751b048 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/org.hl7.fhir.validation.cli/src/main/java/Marker.java b/org.hl7.fhir.validation.cli/src/main/java/Marker.java new file mode 100644 index 000000000..e69de29bb diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index 917bf9caa..8624273ce 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 3.8.24-SNAPSHOT + 4.0.0 ../pom.xml diff --git a/pom.xml b/pom.xml index ebbe265d4..1632c6a01 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 3.8.0-SNAPSHOT + 4.0.0-SNAPSHOT - 3.8.24-SNAPSHOT + 4.0.0 4.0.0-SNAPSHOT From 55928cbda384e990e033fae9180ff2ef63c66c38 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 14 Aug 2019 11:14:30 -0400 Subject: [PATCH 3/6] Bump HAPI FHIR dep to 4.0.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1632c6a01..202b41e92 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 4.0.0-SNAPSHOT + 4.0.0 - 4.0.0 + 4.0.1-SNAPSHOT 4.0.0