001package org.hl7.fhir.dstu3.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import org.hl7.fhir.dstu3.model.*; 035import org.hl7.fhir.exceptions.FHIRException; 036import org.hl7.fhir.instance.model.api.IBaseResource; 037import org.hl7.fhir.dstu3.context.IWorkerContext; 038import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; 039import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent; 040import org.hl7.fhir.utilities.Utilities; 041import org.hl7.fhir.utilities.graphql.*; 042import org.hl7.fhir.utilities.graphql.Argument.ArgumentListStatus; 043import org.hl7.fhir.utilities.graphql.Package; 044import org.hl7.fhir.utilities.graphql.Operation.OperationType; 045 046import java.io.UnsupportedEncodingException; 047import java.net.URLDecoder; 048import java.util.ArrayList; 049import java.util.HashMap; 050import java.util.List; 051import java.util.Map; 052 053import static org.hl7.fhir.utilities.graphql.IGraphQLStorageServices.ReferenceResolution; 054 055public class GraphQLEngine implements IGraphQLEngine { 056 057 public static class SearchEdge extends Base { 058 059 private BundleEntryComponent be; 060 private String type; 061 062 SearchEdge(String type, BundleEntryComponent be) { 063 this.type = type; 064 this.be = be; 065 } 066 @Override 067 public String fhirType() { 068 return type; 069 } 070 071 @Override 072 protected void listChildren(List<Property> result) { 073 throw new Error("Not Implemented"); 074 } 075 076 @Override 077 public String getIdBase() { 078 throw new Error("Not Implemented"); 079 } 080 081 @Override 082 public void setIdBase(String value) { 083 throw new Error("Not Implemented"); 084 } 085 086 @Override 087 public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException { 088 switch (_hash) { 089 case 3357091: /*mode*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasMode() ? be.getSearch().getModeElement() : null); 090 case 109264530: /*score*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasScore() ? be.getSearch().getScoreElement() : null); 091 case -341064690: /*resource*/ return new Property(_name, "resource", "n/a", 0, 1, be.hasResource() ? be.getResource() : null); 092 default: return super.getNamedProperty(_hash, _name, _checkValid); 093 } 094 } 095 } 096 097 public static class SearchWrapper extends Base { 098 099 private Bundle bnd; 100 private String type; 101 private Map<String, String> map; 102 103 SearchWrapper(String type, Bundle bnd) throws FHIRException { 104 this.type = type; 105 this.bnd = bnd; 106 for (BundleLinkComponent bl : bnd.getLink()) 107 if (bl.getRelation().equals("self")) 108 map = parseURL(bl.getUrl()); 109 } 110 111 @Override 112 public String fhirType() { 113 return type; 114 } 115 116 @Override 117 protected void listChildren(List<Property> result) { 118 throw new Error("Not Implemented"); 119 } 120 121 @Override 122 public String getIdBase() { 123 throw new Error("Not Implemented"); 124 } 125 126 @Override 127 public void setIdBase(String value) { 128 throw new Error("Not Implemented"); 129 } 130 131 @Override 132 public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException { 133 switch (_hash) { 134 case 97440432: /*first*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name)); 135 case -1273775369: /*previous*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name)); 136 case 3377907: /*next*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name)); 137 case 3314326: /*last*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name)); 138 case 94851343: /*count*/ return new Property(_name, "integer", "n/a", 0, 1, bnd.getTotalElement()); 139 case -1019779949:/*offset*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("search-offset")); 140 case 860381968: /*pagesize*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("_count")); 141 case 96356950: /*edges*/ return new Property(_name, "edge", "n/a", 0, Integer.MAX_VALUE, getEdges()); 142 default: return super.getNamedProperty(_hash, _name, _checkValid); 143 } 144 } 145 146 private List<Base> getEdges() { 147 List<Base> list = new ArrayList<>(); 148 for (BundleEntryComponent be : bnd.getEntry()) 149 list.add(new SearchEdge(type.substring(0, type.length() - 10) + "Edge", be)); 150 return list; 151 } 152 153 private Base extractParam(String name) throws FHIRException { 154 return map != null ? new IntegerType(map.get(name)) : null; 155 } 156 157 private Map<String, String> parseURL(String url) throws FHIRException { 158 try { 159 Map<String, String> map = new HashMap<String, String>(); 160 String[] pairs = url.split("&"); 161 for (String pair : pairs) { 162 int idx = pair.indexOf("="); 163 String key; 164 key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; 165 String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null; 166 map.put(key, value); 167 } 168 return map; 169 } catch (UnsupportedEncodingException e) { 170 throw new FHIRException(e); 171 } 172 } 173 174 private Base extractLink(String _name) throws FHIRException { 175 for (BundleLinkComponent bl : bnd.getLink()) { 176 if (bl.getRelation().equals(_name)) { 177 Map<String, String> map = parseURL(bl.getUrl()); 178 return new StringType(map.get("search-id")+':'+map.get("search-offset")); 179 } 180 } 181 return null; 182 } 183 184 } 185 186 private IWorkerContext context; 187 188 public GraphQLEngine(IWorkerContext context) { 189 super(); 190 this.context = context; 191 } 192 193 /** 194 * for the host to pass context into and get back on the reference resolution interface 195 */ 196 private Object appInfo; 197 198 /** 199 * the focus resource - if (there instanceof one. if (there isn"t,) there instanceof no focus 200 */ 201 private Resource focus; 202 203 /** 204 * The package that describes the graphQL to be executed, operation name, and variables 205 */ 206 private Package graphQL; 207 208 /** 209 * where the output from executing the query instanceof going to go 210 */ 211 private GraphQLResponse output; 212 213 /** 214 * Application provided reference resolution services 215 */ 216 private IGraphQLStorageServices services; 217 218 // internal stuff 219 private Map<String, Argument> workingVariables = new HashMap<String, Argument>(); 220 221 private FHIRPathEngine fpe; 222 223 private ExpressionNode magicExpression; 224 225 public void execute() throws EGraphEngine, EGraphQLException, FHIRException { 226 if (graphQL == null) 227 throw new EGraphEngine("Unable to process graphql - graphql document missing"); 228 fpe = new FHIRPathEngine(this.context); 229 magicExpression = new ExpressionNode(0); 230 231 output = new GraphQLResponse(); 232 233 Operation op = null; 234 // todo: initial conditions 235 if (!Utilities.noString(graphQL.getOperationName())) { 236 op = graphQL.getDocument().operation(graphQL.getOperationName()); 237 if (op == null) 238 throw new EGraphEngine("Unable to find operation \""+graphQL.getOperationName()+"\""); 239 } else if ((graphQL.getDocument().getOperations().size() == 1)) 240 op = graphQL.getDocument().getOperations().get(0); 241 else 242 throw new EGraphQLException("No operation name provided, so expected to find a single operation"); 243 244 if (op.getOperationType() == OperationType.qglotMutation) 245 throw new EGraphQLException("Mutation operations are not supported (yet)"); 246 247 checkNoDirectives(op.getDirectives()); 248 processVariables(op); 249 if (focus == null) 250 processSearch(output, op.getSelectionSet(), false, ""); 251 else 252 processObject(focus, focus, output, op.getSelectionSet(), false, ""); 253 } 254 255 private boolean checkBooleanDirective(Directive dir) throws EGraphQLException { 256 if (dir.getArguments().size() != 1) 257 throw new EGraphQLException("Unable to process @"+dir.getName()+": expected a single argument \"if\""); 258 if (!dir.getArguments().get(0).getName().equals("if")) 259 throw new EGraphQLException("Unable to process @"+dir.getName()+": expected a single argument \"if\""); 260 List<Value> vl = resolveValues(dir.getArguments().get(0), 1); 261 return vl.get(0).toString().equals("true"); 262 } 263 264 private boolean checkDirectives(List<Directive> directives) throws EGraphQLException { 265 Directive skip = null; 266 Directive include = null; 267 for (Directive dir : directives) { 268 if (dir.getName().equals("skip")) { 269 if ((skip == null)) 270 skip = dir; 271 else 272 throw new EGraphQLException("Duplicate @skip directives"); 273 } else if (dir.getName().equals("include")) { 274 if ((include == null)) 275 include = dir; 276 else 277 throw new EGraphQLException("Duplicate @include directives"); 278 } 279 else if (!Utilities.existsInList(dir.getName(), "flatten", "first", "singleton", "slice")) 280 throw new EGraphQLException("Directive \""+dir.getName()+"\" instanceof not recognised"); 281 } 282 if ((skip != null && include != null)) 283 throw new EGraphQLException("Cannot mix @skip and @include directives"); 284 if (skip != null) 285 return !checkBooleanDirective(skip); 286 else if (include != null) 287 return checkBooleanDirective(include); 288 else 289 return true; 290 } 291 292 private void checkNoDirectives(List<Directive> directives) { 293 294 } 295 296 private boolean targetTypeOk(List<Argument> arguments, IBaseResource dest) throws EGraphQLException { 297 List<String> list = new ArrayList<String>(); 298 for (Argument arg : arguments) { 299 if ((arg.getName().equals("type"))) { 300 List<Value> vl = resolveValues(arg); 301 for (Value v : vl) 302 list.add(v.toString()); 303 } 304 } 305 if (list.size() == 0) 306 return true; 307 else 308 return list.indexOf(dest.fhirType()) > -1; 309 } 310 311 private boolean hasExtensions(Base obj) { 312 if (obj instanceof BackboneElement) 313 return ((BackboneElement) obj).getExtension().size() > 0 || ((BackboneElement) obj).getModifierExtension().size() > 0; 314 else if (obj instanceof DomainResource) 315 return ((DomainResource)obj).getExtension().size() > 0 || ((DomainResource)obj).getModifierExtension().size() > 0; 316 else if (obj instanceof Element) 317 return ((Element)obj).getExtension().size() > 0; 318 else 319 return false; 320 } 321 322 private boolean passesExtensionMode(Base obj, boolean extensionMode) { 323 if (!obj.isPrimitive()) 324 return !extensionMode; 325 else if (extensionMode) 326 return !Utilities.noString(obj.getIdBase()) || hasExtensions(obj); 327 else 328 return obj.primitiveValue() != ""; 329 } 330 331 private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { 332 List<Base> result = new ArrayList<Base>(); 333 if (values.size() > 0) { 334 int count = Integer.MAX_VALUE; 335 int offset = 0; 336 StringBuilder fp = new StringBuilder(); 337 for (Argument arg : arguments) { 338 List<Value> vl = resolveValues(arg); 339 if ((vl.size() != 1)) 340 throw new EGraphQLException("Incorrect number of arguments"); 341 if (values.get(0).isPrimitive()) 342 throw new EGraphQLException("Attempt to use a filter ("+arg.getName()+") on a primtive type ("+prop.getTypeCode()+")"); 343 if ((arg.getName().equals("fhirpath"))) 344 fp.append(" and "+vl.get(0).toString()); 345 else if ((arg.getName().equals("_count"))) 346 count = Integer.valueOf(vl.get(0).toString()); 347 else if ((arg.getName().equals("_offset"))) 348 offset = Integer.valueOf(vl.get(0).toString()); 349 else { 350 Property p = values.get(0).getNamedProperty(arg.getName()); 351 if (p == null) 352 throw new EGraphQLException("Attempt to use an unknown filter ("+arg.getName()+") on a type ("+prop.getTypeCode()+")"); 353 fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); 354 } 355 } 356 int i = 0; 357 int t = 0; 358 if (fp.length() == 0) 359 for (Base v : values) { 360 if ((i >= offset) && passesExtensionMode(v, extensionMode)) { 361 result.add(v); 362 t++; 363 if (t >= count) 364 break; 365 } 366 i++; 367 } else { 368 ExpressionNode node = fpe.parse(fp.toString().substring(5)); 369 for (Base v : values) { 370 if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { 371 result.add(v); 372 t++; 373 if (t >= count) 374 break; 375 } 376 i++; 377 } 378 } 379 } 380 return result; 381 } 382 383 private List<Resource> filterResources(Argument fhirpath, Bundle bnd) throws EGraphQLException, FHIRException { 384 List<Resource> result = new ArrayList<Resource>(); 385 if (bnd.getEntry().size() > 0) { 386 if ((fhirpath == null)) 387 for (BundleEntryComponent be : bnd.getEntry()) 388 result.add(be.getResource()); 389 else { 390 FHIRPathEngine fpe = new FHIRPathEngine(context); 391 ExpressionNode node = fpe.parse(getSingleValue(fhirpath)); 392 for (BundleEntryComponent be : bnd.getEntry()) 393 if (fpe.evaluateToBoolean(null, be.getResource(), be.getResource(), node)) 394 result.add(be.getResource()); 395 } 396 } 397 return result; 398 } 399 400 private List<Resource> filterResources(Argument fhirpath, List<IBaseResource> list) throws EGraphQLException, FHIRException { 401 List<Resource> result = new ArrayList<>(); 402 if (list.size() > 0) { 403 if ((fhirpath == null)) 404 for (IBaseResource v : list) 405 result.add((Resource) v); 406 else { 407 FHIRPathEngine fpe = new FHIRPathEngine(context); 408 ExpressionNode node = fpe.parse(getSingleValue(fhirpath)); 409 for (IBaseResource v : list) 410 if (fpe.evaluateToBoolean(null, (Resource)v, (Resource)v, node)) 411 result.add((Resource) v); 412 } 413 } 414 return result; 415 } 416 417 private boolean hasArgument(List<Argument> arguments, String name, String value) { 418 for (Argument arg : arguments) 419 if ((arg.getName().equals(name)) && arg.hasValue(value)) 420 return true; 421 return false; 422 } 423 424 private void processValues(Resource context, Selection sel, Property prop, ObjectValue target, List<Base> values, boolean extensionMode, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 425 boolean il = false; 426 Argument arg = null; 427 ExpressionNode expression = null; 428 if (sel.getField().hasDirective("slice")) { 429 Directive dir = sel.getField().directive("slice"); 430 String s = ((StringValue) dir.getArguments().get(0).getValues().get(0)).getValue(); 431 if (s.equals("$index")) 432 expression = magicExpression; 433 else 434 expression = fpe.parse(s); 435 } 436 if (sel.getField().hasDirective("flatten")) // special: instruction to drop this node... 437 il = prop.isList() && !sel.getField().hasDirective("first"); 438 else if (sel.getField().hasDirective("first")) { 439 if (expression != null) 440 throw new FHIRException("You cannot mix @slice and @first"); 441 arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), inheritedList)); 442 } else if (expression == null) 443 arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), prop.isList() || inheritedList)); 444 445 446 int index = 0; 447 for (Base value : values) { 448 String ss = ""; 449 if (expression != null) { 450 if (expression == magicExpression) 451 ss = suffix+'.'+Integer.toString(index); 452 else 453 ss = suffix+'.'+fpe.evaluateToString(null, null, value, expression); 454 if (!sel.getField().hasDirective("flatten")) 455 arg = target.addField(sel.getField().getAlias()+suffix, listStatus(sel.getField(), prop.isList() || inheritedList)); 456 } 457 458 if (value.isPrimitive() && !extensionMode) { 459 if (!sel.getField().getSelectionSet().isEmpty()) 460 throw new EGraphQLException("Encountered a selection set on a scalar field type"); 461 processPrimitive(arg, value); 462 } else { 463 if (sel.getField().getSelectionSet().isEmpty()) 464 throw new EGraphQLException("No Fields selected on a complex object"); 465 if (arg == null) 466 processObject(context, value, target, sel.getField().getSelectionSet(), il, ss); 467 else { 468 ObjectValue n = new ObjectValue(); 469 arg.addValue(n); 470 processObject(context, value, n, sel.getField().getSelectionSet(), il, ss); 471 } 472 } 473 if (sel.getField().hasDirective("first")) 474 return; 475 index++; 476 } 477 } 478 479 private void processVariables(Operation op) throws EGraphQLException { 480 for (Variable varRef : op.getVariables()) { 481 Argument varDef = null; 482 for (Argument v : graphQL.getVariables()) 483 if (v.getName().equals(varRef.getName())) 484 varDef = v; 485 if (varDef != null) 486 workingVariables.put(varRef.getName(), varDef); // todo: check type? 487 else if (varRef.getDefaultValue() != null) 488 workingVariables.put(varRef.getName(), new Argument(varRef.getName(), varRef.getDefaultValue())); 489 else 490 throw new EGraphQLException("No value found for variable "); 491 } 492 } 493 494 private boolean isPrimitive(String typename) { 495 return Utilities.existsInList(typename, "boolean", "integer", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "url", "canonical"); 496 } 497 498 private boolean isResourceName(String name, String suffix) { 499 if (!name.endsWith(suffix)) 500 return false; 501 name = name.substring(0, name.length()-suffix.length()); 502 return context.getResourceNamesAsSet().contains(name); 503 } 504 505 private void processObject(Resource context, Base source, ObjectValue target, List<Selection> selection, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 506 for (Selection sel : selection) { 507 if (sel.getField() != null) { 508 if (checkDirectives(sel.getField().getDirectives())) { 509 Property prop = source.getNamedProperty(sel.getField().getName()); 510 if ((prop == null) && sel.getField().getName().startsWith("_")) 511 prop = source.getNamedProperty(sel.getField().getName().substring(1)); 512 if (prop == null) { 513 if ((sel.getField().getName().equals("resourceType") && source instanceof Resource)) 514 target.addField("resourceType", listStatus(sel.getField(), false)).addValue(new StringValue(source.fhirType())); 515 else if ((sel.getField().getName().equals("resource") && source.fhirType().equals("Reference"))) 516 processReference(context, source, sel.getField(), target, inheritedList, suffix); 517 else if ((sel.getField().getName().equals("resource") && source.fhirType().equals("canonical"))) 518 processCanonicalReference(context, source, sel.getField(), target, inheritedList, suffix); 519 else if (isResourceName(sel.getField().getName(), "List") && (source instanceof Resource)) 520 processReverseReferenceList((Resource) source, sel.getField(), target, inheritedList, suffix); 521 else if (isResourceName(sel.getField().getName(), "Connection") && (source instanceof Resource)) 522 processReverseReferenceSearch((Resource) source, sel.getField(), target, inheritedList, suffix); 523 else 524 throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); 525 } else { 526 if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) 527 throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); 528 529 List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); 530 if (!vl.isEmpty()) 531 processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); 532 } 533 } 534 } else if (sel.getInlineFragment() != null) { 535 if (checkDirectives(sel.getInlineFragment().getDirectives())) { 536 if (Utilities.noString(sel.getInlineFragment().getTypeCondition())) 537 throw new EGraphQLException("Not done yet - inline fragment with no type condition"); // cause why? why instanceof it even valid? 538 if (source.fhirType().equals(sel.getInlineFragment().getTypeCondition())) 539 processObject(context, source, target, sel.getInlineFragment().getSelectionSet(), inheritedList, suffix); 540 } 541 } else if (checkDirectives(sel.getFragmentSpread().getDirectives())) { 542 Fragment fragment = graphQL.getDocument().fragment(sel.getFragmentSpread().getName()); 543 if (fragment == null) 544 throw new EGraphQLException("Unable to resolve fragment "+sel.getFragmentSpread().getName()); 545 546 if (Utilities.noString(fragment.getTypeCondition())) 547 throw new EGraphQLException("Not done yet - inline fragment with no type condition"); // cause why? why instanceof it even valid? 548 if (source.fhirType().equals(fragment.getTypeCondition())) 549 processObject(context, source, target, fragment.getSelectionSet(), inheritedList, suffix); 550 } 551 } 552 } 553 554 private void processPrimitive(Argument arg, Base value) { 555 String s = value.fhirType(); 556 if (s.equals("integer") || s.equals("decimal") || s.equals("unsignedInt") || s.equals("positiveInt")) 557 arg.addValue(new NumberValue(value.primitiveValue())); 558 else if (s.equals("boolean")) 559 arg.addValue(new NameValue(value.primitiveValue())); 560 else 561 arg.addValue(new StringValue(value.primitiveValue())); 562 } 563 564 private void processReference(Resource context, Base source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 565 if (!(source instanceof Reference)) 566 throw new EGraphQLException("Not done yet"); 567 if (services == null) 568 throw new EGraphQLException("Resource Referencing services not provided"); 569 570 Reference ref = (Reference) source; 571 ReferenceResolution res = services.lookup(appInfo, context, ref); 572 if (res != null) { 573 if (targetTypeOk(field.getArguments(), res.getTarget())) { 574 Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList)); 575 ObjectValue obj = new ObjectValue(); 576 arg.addValue(obj); 577 processObject((Resource)res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix); 578 } 579 } 580 else if (!hasArgument(field.getArguments(), "optional", "true")) 581 throw new EGraphQLException("Unable to resolve reference to "+ref.getReference()); 582 } 583 584 private void processCanonicalReference(Resource context, Base source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 585 if (services == null) 586 throw new EGraphQLException("Resource Referencing services not provided"); 587 588 Reference ref = new Reference(source.primitiveValue()); 589 ReferenceResolution res = services.lookup(appInfo, context, ref); 590 if (res != null) { 591 if (targetTypeOk(field.getArguments(), res.getTarget())) { 592 Argument arg = target.addField(field.getAlias() + suffix, listStatus(field, inheritedList)); 593 ObjectValue obj = new ObjectValue(); 594 arg.addValue(obj); 595 processObject((Resource)res.getTargetContext(), (Base) res.getTarget(), obj, field.getSelectionSet(), inheritedList, suffix); 596 } 597 } 598 else if (!hasArgument(field.getArguments(), "optional", "true")) 599 throw new EGraphQLException("Unable to resolve reference to "+ref.getReference()); 600 } 601 602 private ArgumentListStatus listStatus(Field field, boolean isList) { 603 if (field.hasDirective("singleton")) 604 return ArgumentListStatus.SINGLETON; 605 else if (isList) 606 return ArgumentListStatus.REPEATING; 607 else 608 return ArgumentListStatus.NOT_SPECIFIED; 609 } 610 611 private void processReverseReferenceList(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 612 if (services == null) 613 throw new EGraphQLException("Resource Referencing services not provided"); 614 List<IBaseResource> list = new ArrayList<>(); 615 List<Argument> params = new ArrayList<>(); 616 Argument parg = null; 617 for (Argument a : field.getArguments()) 618 if (!(a.getName().equals("_reference"))) 619 params.add(a); 620 else if ((parg == null)) 621 parg = a; 622 else 623 throw new EGraphQLException("Duplicate parameter _reference"); 624 if (parg == null) 625 throw new EGraphQLException("Missing parameter _reference"); 626 Argument arg = new Argument(); 627 params.add(arg); 628 arg.setName(getSingleValue(parg)); 629 arg.addValue(new StringValue(source.fhirType()+"/"+source.getId())); 630 services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list); 631 arg = null; 632 ObjectValue obj = null; 633 634 List<Resource> vl = filterResources(field.argument("fhirpath"), list); 635 if (!vl.isEmpty()) { 636 arg = target.addField(field.getAlias()+suffix, listStatus(field, true)); 637 for (Resource v : vl) { 638 obj = new ObjectValue(); 639 arg.addValue(obj); 640 processObject(v, v, obj, field.getSelectionSet(), inheritedList, suffix); 641 } 642 } 643 } 644 645 private void processReverseReferenceSearch(Resource source, Field field, ObjectValue target, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 646 if (services == null) 647 throw new EGraphQLException("Resource Referencing services not provided"); 648 List<Argument> params = new ArrayList<Argument>(); 649 Argument parg = null; 650 for (Argument a : field.getArguments()) 651 if (!(a.getName().equals("_reference"))) 652 params.add(a); 653 else if ((parg == null)) 654 parg = a; 655 else 656 throw new EGraphQLException("Duplicate parameter _reference"); 657 if (parg == null) 658 throw new EGraphQLException("Missing parameter _reference"); 659 Argument arg = new Argument(); 660 params.add(arg); 661 arg.setName(getSingleValue(parg)); 662 arg.addValue(new StringValue(source.fhirType()+"/"+source.getId())); 663 Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); 664 Base bndWrapper = new SearchWrapper(field.getName(), bnd); 665 arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); 666 ObjectValue obj = new ObjectValue(); 667 arg.addValue(obj); 668 processObject(null, bndWrapper, obj, field.getSelectionSet(), inheritedList, suffix); 669 } 670 671 private void processSearch(ObjectValue target, List<Selection> selection, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 672 for (Selection sel : selection) { 673 if ((sel.getField() == null)) 674 throw new EGraphQLException("Only field selections are allowed in this context"); 675 checkNoDirectives(sel.getField().getDirectives()); 676 677 if ((isResourceName(sel.getField().getName(), ""))) 678 processSearchSingle(target, sel.getField(), inheritedList, suffix); 679 else if ((isResourceName(sel.getField().getName(), "List"))) 680 processSearchSimple(target, sel.getField(), inheritedList, suffix); 681 else if ((isResourceName(sel.getField().getName(), "Connection"))) 682 processSearchFull(target, sel.getField(), inheritedList, suffix); 683 } 684 } 685 686 private void processSearchSingle(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 687 if (services == null) 688 throw new EGraphQLException("Resource Referencing services not provided"); 689 String id = ""; 690 for (Argument arg : field.getArguments()) 691 if ((arg.getName().equals("id"))) 692 id = getSingleValue(arg); 693 else 694 throw new EGraphQLException("Unknown/invalid parameter "+arg.getName()); 695 if (Utilities.noString(id)) 696 throw new EGraphQLException("No id found"); 697 Resource res = (Resource) services.lookup(appInfo, field.getName(), id); 698 if (res == null) 699 throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found"); 700 Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); 701 ObjectValue obj = new ObjectValue(); 702 arg.addValue(obj); 703 processObject(res, res, obj, field.getSelectionSet(), inheritedList, suffix); 704 } 705 706 private void processSearchSimple(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 707 if (services == null) 708 throw new EGraphQLException("Resource Referencing services not provided"); 709 List<IBaseResource> list = new ArrayList<>(); 710 services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list); 711 Argument arg = null; 712 ObjectValue obj = null; 713 714 List<Resource> vl = filterResources(field.argument("fhirpath"), list); 715 if (!vl.isEmpty()) { 716 arg = target.addField(field.getAlias()+suffix, listStatus(field, true)); 717 for (Resource v : vl) { 718 obj = new ObjectValue(); 719 arg.addValue(obj); 720 processObject(v, v, obj, field.getSelectionSet(), inheritedList, suffix); 721 } 722 } 723 } 724 725 private void processSearchFull(ObjectValue target, Field field, boolean inheritedList, String suffix) throws EGraphQLException, FHIRException { 726 if (services == null) 727 throw new EGraphQLException("Resource Referencing services not provided"); 728 List<Argument> params = new ArrayList<Argument>(); 729 Argument carg = null; 730 for ( Argument arg : field.getArguments()) 731 if (arg.getName().equals("cursor")) 732 carg = arg; 733 else 734 params.add(arg); 735 if ((carg != null)) { 736 params.clear();; 737 String[] parts = getSingleValue(carg).split(":"); 738 params.add(new Argument("search-id", new StringValue(parts[0]))); 739 params.add(new Argument("search-offset", new StringValue(parts[1]))); 740 } 741 742 Bundle bnd = (Bundle) services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params); 743 SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd); 744 Argument arg = target.addField(field.getAlias()+suffix, listStatus(field, false)); 745 ObjectValue obj = new ObjectValue(); 746 arg.addValue(obj); 747 processObject(null, bndWrapper, obj, field.getSelectionSet(), inheritedList, suffix); 748 } 749 750 private String getSingleValue(Argument arg) throws EGraphQLException { 751 List<Value> vl = resolveValues(arg, 1); 752 if (vl.size() == 0) 753 return ""; 754 return vl.get(0).toString(); 755 } 756 757 private List<Value> resolveValues(Argument arg) throws EGraphQLException { 758 return resolveValues(arg, -1, ""); 759 } 760 761 private List<Value> resolveValues(Argument arg, int max) throws EGraphQLException { 762 return resolveValues(arg, max, ""); 763 } 764 765 private List<Value> resolveValues(Argument arg, int max, String vars) throws EGraphQLException { 766 List<Value> result = new ArrayList<Value>(); 767 for (Value v : arg.getValues()) { 768 if (! (v instanceof VariableValue)) 769 result.add(v); 770 else { 771 if (vars.contains(":"+v.toString()+":")) 772 throw new EGraphQLException("Recursive reference to variable "+v.toString()); 773 Argument a = workingVariables.get(v.toString()); 774 if (a == null) 775 throw new EGraphQLException("No value found for variable \""+v.toString()+"\" in \""+arg.getName()+"\""); 776 List<Value> vl = resolveValues(a, -1, vars+":"+v.toString()+":"); 777 result.addAll(vl); 778 } 779 } 780 if ((max != -1 && result.size() > max)) 781 throw new EGraphQLException("Only "+Integer.toString(max)+" values are allowed for \""+arg.getName()+"\", but "+Integer.toString(result.size())+" enoucntered"); 782 return result; 783 } 784 785 786 787 788 public Object getAppInfo() { 789 return appInfo; 790 } 791 792 public void setAppInfo(Object appInfo) { 793 this.appInfo = appInfo; 794 } 795 796 public Resource getFocus() { 797 return focus; 798 } 799 800 @Override 801 public void setFocus(IBaseResource focus) { 802 this.focus = (Resource) focus; 803 } 804 805 public Package getGraphQL() { 806 return graphQL; 807 } 808 809 @Override 810 public void setGraphQL(Package graphQL) { 811 this.graphQL = graphQL; 812 } 813 814 public GraphQLResponse getOutput() { 815 return output; 816 } 817 818 public IGraphQLStorageServices getServices() { 819 return services; 820 } 821 822 @Override 823 public void setServices(IGraphQLStorageServices services) { 824 this.services = services; 825 } 826 827 828 // 829//{ GraphQLSearchWrapper } 830// 831//constructor GraphQLSearchWrapper.Create(bundle : Bundle); 832//var 833// s : String; 834//{ 835// inherited Create; 836// FBundle = bundle; 837// s = bundle_List.Matches["self"]; 838// FParseMap = TParseMap.create(s.Substring(s.IndexOf("?")+1)); 839//} 840// 841//destructor GraphQLSearchWrapper.Destroy; 842//{ 843// FParseMap.free; 844// FBundle.Free; 845// inherited; 846//} 847// 848//function GraphQLSearchWrapper.extractLink(name: String): String; 849//var 850// s : String; 851// pm : TParseMap; 852//{ 853// s = FBundle_List.Matches[name]; 854// if (s == "") 855// result = null 856// else 857// { 858// pm = TParseMap.create(s.Substring(s.IndexOf("?")+1)); 859// try 860// result = String.Create(pm.GetVar("search-id")+":"+pm.GetVar("search-offset")); 861// finally 862// pm.Free; 863// } 864// } 865//} 866// 867//function GraphQLSearchWrapper.extractParam(name: String; int : boolean): Base; 868//var 869// s : String; 870//{ 871// s = FParseMap.GetVar(name); 872// if (s == "") 873// result = null 874// else if (int) 875// result = Integer.Create(s) 876// else 877// result = String.Create(s); 878//} 879// 880//function GraphQLSearchWrapper.fhirType(): String; 881//{ 882// result = "*Connection"; 883//} 884// 885// // 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 886// 887//function GraphQLSearchWrapper.getPropertyValue(propName: string): Property; 888//var 889// list : List<GraphQLSearchEdge>; 890// be : BundleEntry; 891//{ 892// if (propName == "first") 893// result = Property.Create(self, propname, "string", false, String, extractLink("first")) 894// else if (propName == "previous") 895// result = Property.Create(self, propname, "string", false, String, extractLink("previous")) 896// else if (propName == "next") 897// result = Property.Create(self, propname, "string", false, String, extractLink("next")) 898// else if (propName == "last") 899// result = Property.Create(self, propname, "string", false, String, extractLink("last")) 900// else if (propName == "count") 901// result = Property.Create(self, propname, "integer", false, String, FBundle.totalElement) 902// else if (propName == "offset") 903// result = Property.Create(self, propname, "integer", false, Integer, extractParam("search-offset", true)) 904// else if (propName == "pagesize") 905// result = Property.Create(self, propname, "integer", false, Integer, extractParam("_count", true)) 906// else if (propName == "edges") 907// { 908// list = ArrayList<GraphQLSearchEdge>(); 909// try 910// for be in FBundle.getEntry() do 911// list.add(GraphQLSearchEdge.create(be)); 912// result = Property.Create(self, propname, "integer", true, Integer, List<Base>(list)); 913// finally 914// list.Free; 915// } 916// } 917// else 918// result = null; 919//} 920// 921//private void GraphQLSearchWrapper.SetBundle(const Value: Bundle); 922//{ 923// FBundle.Free; 924// FBundle = Value; 925//} 926// 927//{ GraphQLSearchEdge } 928// 929//constructor GraphQLSearchEdge.Create(entry: BundleEntry); 930//{ 931// inherited Create; 932// FEntry = entry; 933//} 934// 935//destructor GraphQLSearchEdge.Destroy; 936//{ 937// FEntry.Free; 938// inherited; 939//} 940// 941//function GraphQLSearchEdge.fhirType(): String; 942//{ 943// result = "*Edge"; 944//} 945// 946//function GraphQLSearchEdge.getPropertyValue(propName: string): Property; 947//{ 948// if (propName == "mode") 949// { 950// if (FEntry.search != null) 951// result = Property.Create(self, propname, "code", false, Enum, FEntry.search.modeElement) 952// else 953// result = Property.Create(self, propname, "code", false, Enum, Base(null)); 954// } 955// else if (propName == "score") 956// { 957// if (FEntry.search != null) 958// result = Property.Create(self, propname, "decimal", false, Decimal, FEntry.search.scoreElement) 959// else 960// result = Property.Create(self, propname, "decimal", false, Decimal, Base(null)); 961// } 962// else if (propName == "resource") 963// result = Property.Create(self, propname, "resource", false, Resource, FEntry.getResource()) 964// else 965// result = null; 966//} 967// 968//private void GraphQLSearchEdge.SetEntry(const Value: BundleEntry); 969//{ 970// FEntry.Free; 971// FEntry = value; 972//} 973// 974}