001package ca.uhn.fhir.rest.client.impl; 002 003/* 004 * #%L 005 * HAPI FHIR - Client Framework 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.*; 024import ca.uhn.fhir.model.api.IQueryParameterType; 025import ca.uhn.fhir.model.api.IResource; 026import ca.uhn.fhir.model.api.Include; 027import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 028import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; 029import ca.uhn.fhir.model.primitive.IdDt; 030import ca.uhn.fhir.model.primitive.InstantDt; 031import ca.uhn.fhir.model.primitive.UriDt; 032import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; 033import ca.uhn.fhir.parser.DataFormatException; 034import ca.uhn.fhir.parser.IParser; 035import ca.uhn.fhir.rest.api.*; 036import ca.uhn.fhir.rest.client.api.IGenericClient; 037import ca.uhn.fhir.rest.client.api.IHttpClient; 038import ca.uhn.fhir.rest.client.api.IHttpRequest; 039import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; 040import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; 041import ca.uhn.fhir.rest.client.method.*; 042import ca.uhn.fhir.rest.gclient.*; 043import ca.uhn.fhir.rest.param.DateParam; 044import ca.uhn.fhir.rest.param.DateRangeParam; 045import ca.uhn.fhir.rest.param.TokenParam; 046import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 047import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 048import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; 049import ca.uhn.fhir.util.ICallable; 050import ca.uhn.fhir.util.ParametersUtil; 051import ca.uhn.fhir.util.UrlUtil; 052import com.google.common.base.Charsets; 053import org.apache.commons.io.IOUtils; 054import org.apache.commons.lang3.StringUtils; 055import org.apache.commons.lang3.Validate; 056import org.hl7.fhir.instance.model.api.*; 057 058import java.io.IOException; 059import java.io.InputStream; 060import java.util.*; 061import java.util.Map.Entry; 062 063import static org.apache.commons.lang3.StringUtils.*; 064 065/** 066 * @author James Agnew 067 * @author Doug Martin (Regenstrief Center for Biomedical Informatics) 068 */ 069public class GenericClient extends BaseClient implements IGenericClient { 070 071 private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = GenericClient.class.getName() + ".cannotDetermineResourceTypeFromUri"; 072 private static final String I18N_INCOMPLETE_URI_FOR_READ = GenericClient.class.getName() + ".incompleteUriForRead"; 073 private static final String I18N_NO_VERSION_ID_FOR_VREAD = GenericClient.class.getName() + ".noVersionIdForVread"; 074 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class); 075 private FhirContext myContext; 076 private IHttpRequest myLastRequest; 077 private boolean myLogRequestAndResponse; 078 079 /** 080 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 081 */ 082 public GenericClient(FhirContext theContext, IHttpClient theHttpClient, String theServerBase, RestfulClientFactory theFactory) { 083 super(theHttpClient, theServerBase, theFactory); 084 myContext = theContext; 085 } 086 087 @Override 088 public IFetchConformanceUntyped capabilities() { 089 return new FetchConformanceInternal(); 090 } 091 092 @Override 093 public ICreate create() { 094 return new CreateInternal(); 095 } 096 097 @Override 098 public IDelete delete() { 099 return new DeleteInternal(); 100 } 101 102 private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IIdType theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, 103 SummaryEnum theSummary, EncodingEnum theEncoding, Set<String> theSubsetElements, String theCustomAcceptHeaderValue, 104 Map<String, List<String>> theCustomHeaders) { 105 String resName = toResourceName(theType); 106 IIdType id = theId; 107 if (!id.hasBaseUrl()) { 108 id = new IdDt(resName, id.getIdPart(), id.getVersionIdPart()); 109 } 110 111 HttpGetClientInvocation invocation; 112 if (id.hasBaseUrl()) { 113 if (theVRead) { 114 invocation = ReadMethodBinding.createAbsoluteVReadInvocation(getFhirContext(), id); 115 } else { 116 invocation = ReadMethodBinding.createAbsoluteReadInvocation(getFhirContext(), id); 117 } 118 } else { 119 if (theVRead) { 120 invocation = ReadMethodBinding.createVReadInvocation(getFhirContext(), id, resName); 121 } else { 122 invocation = ReadMethodBinding.createReadInvocation(getFhirContext(), id, resName); 123 } 124 } 125 if (isKeepResponses()) { 126 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(theCustomAcceptHeaderValue), getEncoding(), isPrettyPrint()); 127 } 128 129 if (theIfVersionMatches != null) { 130 invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); 131 } 132 133 boolean allowHtmlResponse = SummaryEnum.TEXT.equals(theSummary); 134 ResourceResponseHandler<T> binding = new ResourceResponseHandler<>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse); 135 136 if (theNotModifiedHandler == null) { 137 return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue, theCustomHeaders); 138 } 139 try { 140 return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null, theCustomAcceptHeaderValue, theCustomHeaders); 141 } catch (NotModifiedException e) { 142 return theNotModifiedHandler.call(); 143 } 144 145 } 146 147 @Override 148 public IFetchConformanceUntyped fetchConformance() { 149 return new FetchConformanceInternal(); 150 } 151 152 @Override 153 public void forceConformanceCheck() { 154 super.forceConformanceCheck(); 155 } 156 157 @Override 158 public FhirContext getFhirContext() { 159 return myContext; 160 } 161 162 public IHttpRequest getLastRequest() { 163 return myLastRequest; 164 } 165 166 /** 167 * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! 168 */ 169 public void setLastRequest(IHttpRequest theLastRequest) { 170 myLastRequest = theLastRequest; 171 } 172 173 protected String getPreferredId(IBaseResource theResource, String theId) { 174 if (isNotBlank(theId)) { 175 return theId; 176 } 177 return theResource.getIdElement().getIdPart(); 178 } 179 180 @Override 181 public IHistory history() { 182 return new HistoryInternal(); 183 } 184 185 /** 186 * @deprecated Use {@link LoggingInterceptor} as a client interceptor registered to your 187 * client instead, as this provides much more fine-grained control over what is logged. This 188 * method will be removed at some point (deprecated in HAPI 1.6 - 2016-06-16) 189 */ 190 @Deprecated 191 public boolean isLogRequestAndResponse() { 192 return myLogRequestAndResponse; 193 } 194 195 @Deprecated // override deprecated method 196 @Override 197 public void setLogRequestAndResponse(boolean theLogRequestAndResponse) { 198 myLogRequestAndResponse = theLogRequestAndResponse; 199 } 200 201 @Override 202 public IGetPage loadPage() { 203 return new LoadPageInternal(); 204 } 205 206 @Override 207 public IMeta meta() { 208 return new MetaInternal(); 209 } 210 211 @Override 212 public IOperation operation() { 213 return new OperationInternal(); 214 } 215 216 @Override 217 public IPatch patch() { 218 return new PatchInternal(); 219 } 220 221 @Override 222 public IRead read() { 223 return new ReadInternal(); 224 } 225 226 @Override 227 public <T extends IBaseResource> T read(Class<T> theType, String theId) { 228 return read(theType, new IdDt(theId)); 229 } 230 231 @Override 232 public <T extends IBaseResource> T read(final Class<T> theType, UriDt theUrl) { 233 IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl); 234 return doReadOrVRead(theType, id, false, null, null, false, null, null, null, null, null); 235 } 236 237 @Override 238 public IBaseResource read(UriDt theUrl) { 239 IdDt id = new IdDt(theUrl); 240 String resourceType = id.getResourceType(); 241 if (isBlank(resourceType)) { 242 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, theUrl.getValueAsString())); 243 } 244 RuntimeResourceDefinition def = myContext.getResourceDefinition(resourceType); 245 if (def == null) { 246 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString())); 247 } 248 return read(def.getImplementingClass(), id); 249 } 250 251 @SuppressWarnings({"rawtypes", "unchecked"}) 252 @Override 253 public IUntypedQuery search() { 254 return new SearchInternal(); 255 } 256 257 private String toResourceName(Class<? extends IBaseResource> theType) { 258 return myContext.getResourceDefinition(theType).getName(); 259 } 260 261 @Override 262 public ITransaction transaction() { 263 return new TransactionInternal(); 264 } 265 266 @Override 267 public IUpdate update() { 268 return new UpdateInternal(); 269 } 270 271 @Override 272 public MethodOutcome update(IdDt theIdDt, IBaseResource theResource) { 273 BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext); 274 if (isKeepResponses()) { 275 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(null), getEncoding(), isPrettyPrint()); 276 } 277 278 OutcomeResponseHandler binding = new OutcomeResponseHandler(); 279 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 280 return resp; 281 } 282 283 @Override 284 public MethodOutcome update(String theId, IBaseResource theResource) { 285 return update(new IdDt(theId), theResource); 286 } 287 288 @Override 289 public IValidate validate() { 290 return new ValidateInternal(); 291 } 292 293 @Override 294 public MethodOutcome validate(IBaseResource theResource) { 295 BaseHttpClientInvocation invocation; 296 invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, theResource); 297 298 if (isKeepResponses()) { 299 myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(null), getEncoding(), isPrettyPrint()); 300 } 301 302 OutcomeResponseHandler binding = new OutcomeResponseHandler(); 303 MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); 304 return resp; 305 } 306 307 @Override 308 public <T extends IBaseResource> T vread(final Class<T> theType, IdDt theId) { 309 if (!theId.hasVersionIdPart()) { 310 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue())); 311 } 312 return doReadOrVRead(theType, theId, true, null, null, false, null, null, null, null, null); 313 } 314 315 @Override 316 public <T extends IBaseResource> T vread(Class<T> theType, String theId, String theVersionId) { 317 IdDt resId = new IdDt(toResourceName(theType), theId, theVersionId); 318 return vread(theType, resId); 319 } 320 321 private enum MetaOperation { 322 ADD, 323 DELETE, 324 GET 325 } 326 327 private abstract class BaseClientExecutable<T extends IClientExecutable<?, Y>, Y> implements IClientExecutable<T, Y> { 328 329 EncodingEnum myParamEncoding; 330 Boolean myPrettyPrint; 331 SummaryEnum mySummaryMode; 332 CacheControlDirective myCacheControlDirective; 333 Map<String, List<String>> myCustomHeaderValues = new HashMap<>(); 334 private String myCustomAcceptHeaderValue; 335 private List<Class<? extends IBaseResource>> myPreferResponseTypes; 336 private boolean myQueryLogRequestAndResponse; 337 private Set<String> mySubsetElements; 338 339 public String getCustomAcceptHeaderValue() { 340 return myCustomAcceptHeaderValue; 341 } 342 343 @SuppressWarnings("unchecked") 344 @Override 345 public T accept(String theHeaderValue) { 346 myCustomAcceptHeaderValue = theHeaderValue; 347 return (T) this; 348 } 349 350 @Deprecated // override deprecated method 351 @SuppressWarnings("unchecked") 352 @Override 353 public T andLogRequestAndResponse(boolean theLogRequestAndResponse) { 354 myQueryLogRequestAndResponse = theLogRequestAndResponse; 355 return (T) this; 356 } 357 358 @SuppressWarnings("unchecked") 359 @Override 360 public T cacheControl(CacheControlDirective theCacheControlDirective) { 361 myCacheControlDirective = theCacheControlDirective; 362 return (T) this; 363 } 364 365 @SuppressWarnings("unchecked") 366 @Override 367 public T elementsSubset(String... theElements) { 368 if (theElements != null && theElements.length > 0) { 369 mySubsetElements = new HashSet<>(Arrays.asList(theElements)); 370 } else { 371 mySubsetElements = null; 372 } 373 return (T) this; 374 } 375 376 @SuppressWarnings("unchecked") 377 @Override 378 public T encoded(EncodingEnum theEncoding) { 379 Validate.notNull(theEncoding, "theEncoding must not be null"); 380 myParamEncoding = theEncoding; 381 return (T) this; 382 } 383 384 @SuppressWarnings("unchecked") 385 @Override 386 public T encodedJson() { 387 myParamEncoding = EncodingEnum.JSON; 388 return (T) this; 389 } 390 391 @SuppressWarnings("unchecked") 392 @Override 393 public T encodedXml() { 394 myParamEncoding = EncodingEnum.XML; 395 return (T) this; 396 } 397 398 @SuppressWarnings("unchecked") 399 @Override 400 public T withAdditionalHeader(String theHeaderName, String theHeaderValue) { 401 Objects.requireNonNull(theHeaderName, "headerName cannot be null"); 402 Objects.requireNonNull(theHeaderValue, "headerValue cannot be null"); 403 if (!myCustomHeaderValues.containsKey(theHeaderName)) { 404 myCustomHeaderValues.put(theHeaderName, new ArrayList<>()); 405 } 406 myCustomHeaderValues.get(theHeaderName).add(theHeaderValue); 407 return (T) this; 408 } 409 410 protected EncodingEnum getParamEncoding() { 411 return myParamEncoding; 412 } 413 414 public List<Class<? extends IBaseResource>> getPreferResponseTypes() { 415 return myPreferResponseTypes; 416 } 417 418 public List<Class<? extends IBaseResource>> getPreferResponseTypes(Class<? extends IBaseResource> theDefault) { 419 if (myPreferResponseTypes != null) { 420 return myPreferResponseTypes; 421 } 422 return toTypeList(theDefault); 423 } 424 425 protected Set<String> getSubsetElements() { 426 return mySubsetElements; 427 } 428 429 protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) { 430 if (isKeepResponses()) { 431 myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint); 432 } 433 434 Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective, myCustomAcceptHeaderValue, myCustomHeaderValues); 435 return resp; 436 } 437 438 protected IBaseResource parseResourceBody(String theResourceBody) { 439 EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(theResourceBody); 440 if (encoding == null) { 441 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 442 } 443 return encoding.newParser(myContext).parseResource(theResourceBody); 444 } 445 446 @SuppressWarnings("unchecked") 447 @Override 448 public T preferResponseType(Class<? extends IBaseResource> theClass) { 449 myPreferResponseTypes = null; 450 if (theClass != null) { 451 myPreferResponseTypes = new ArrayList<>(); 452 myPreferResponseTypes.add(theClass); 453 } 454 return (T) this; 455 } 456 457 @SuppressWarnings("unchecked") 458 @Override 459 public T preferResponseTypes(List<Class<? extends IBaseResource>> theClass) { 460 myPreferResponseTypes = theClass; 461 return (T) this; 462 } 463 464 @SuppressWarnings("unchecked") 465 @Override 466 public T prettyPrint() { 467 myPrettyPrint = true; 468 return (T) this; 469 } 470 471 @SuppressWarnings("unchecked") 472 @Override 473 public T summaryMode(SummaryEnum theSummary) { 474 mySummaryMode = theSummary; 475 return ((T) this); 476 } 477 478 } 479 480 private abstract class BaseSearch<EXEC extends IClientExecutable<?, OUTPUT>, QUERY extends IBaseQuery<QUERY>, OUTPUT> extends BaseClientExecutable<EXEC, OUTPUT> implements IBaseQuery<QUERY> { 481 482 private Map<String, List<String>> myParams = new LinkedHashMap<>(); 483 484 @Override 485 public QUERY and(ICriterion<?> theCriterion) { 486 return where(theCriterion); 487 } 488 489 public Map<String, List<String>> getParamMap() { 490 return myParams; 491 } 492 493 @SuppressWarnings("unchecked") 494 @Override 495 public QUERY where(ICriterion<?> theCriterion) { 496 ICriterionInternal criterion = (ICriterionInternal) theCriterion; 497 498 String parameterName = criterion.getParameterName(); 499 String parameterValue = criterion.getParameterValue(myContext); 500 if (isNotBlank(parameterValue)) { 501 addParam(myParams, parameterName, parameterValue); 502 } 503 504 return (QUERY) this; 505 } 506 507 @Override 508 public QUERY whereMap(Map<String, List<String>> theRawMap) { 509 if (theRawMap != null) { 510 for (String nextKey : theRawMap.keySet()) { 511 for (String nextValue : theRawMap.get(nextKey)) { 512 addParam(myParams, nextKey, nextValue); 513 } 514 } 515 } 516 517 return (QUERY) this; 518 } 519 520 @SuppressWarnings("unchecked") 521 @Override 522 public QUERY where(Map<String, List<IQueryParameterType>> theCriterion) { 523 Validate.notNull(theCriterion, "theCriterion must not be null"); 524 for (Entry<String, List<IQueryParameterType>> nextEntry : theCriterion.entrySet()) { 525 String nextKey = nextEntry.getKey(); 526 List<IQueryParameterType> nextValues = nextEntry.getValue(); 527 for (IQueryParameterType nextValue : nextValues) { 528 addParam(myParams, nextKey, nextValue.getValueAsQueryToken(myContext)); 529 } 530 } 531 return (QUERY) this; 532 } 533 534 } 535 536 private class CreateInternal extends BaseSearch<ICreateTyped, ICreateWithQueryTyped, MethodOutcome> implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped { 537 538 private boolean myConditional; 539 private PreferReturnEnum myPrefer; 540 private IBaseResource myResource; 541 private String myResourceBody; 542 private String mySearchUrl; 543 544 @Override 545 public ICreateWithQuery conditional() { 546 myConditional = true; 547 return this; 548 } 549 550 @Override 551 public ICreateTyped conditionalByUrl(String theSearchUrl) { 552 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 553 return this; 554 } 555 556 @Override 557 public MethodOutcome execute() { 558 if (myResource == null) { 559 myResource = parseResourceBody(myResourceBody); 560 } 561 562 // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding 563 if (getParamEncoding() != null) { 564 myResourceBody = null; 565 } 566 567 BaseHttpClientInvocation invocation; 568 if (mySearchUrl != null) { 569 invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myContext, mySearchUrl); 570 } else if (myConditional) { 571 invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myContext, getParamMap()); 572 } else { 573 invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myContext); 574 } 575 576 addPreferHeader(myPrefer, invocation); 577 578 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 579 580 Map<String, List<String>> params = new HashMap<String, List<String>>(); 581 return invoke(params, binding, invocation); 582 583 } 584 585 @Override 586 public ICreateTyped prefer(PreferReturnEnum theReturn) { 587 myPrefer = theReturn; 588 return this; 589 } 590 591 @Override 592 public ICreateTyped resource(IBaseResource theResource) { 593 Validate.notNull(theResource, "Resource can not be null"); 594 myResource = theResource; 595 return this; 596 } 597 598 @Override 599 public ICreateTyped resource(String theResourceBody) { 600 Validate.notBlank(theResourceBody, "Body can not be null or blank"); 601 myResourceBody = theResourceBody; 602 return this; 603 } 604 605 } 606 607 private class DeleteInternal extends BaseSearch<IDeleteTyped, IDeleteWithQueryTyped, IBaseOperationOutcome> implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { 608 609 private boolean myConditional; 610 private IIdType myId; 611 private String myResourceType; 612 private String mySearchUrl; 613 614 @Override 615 public IBaseOperationOutcome execute() { 616 HttpDeleteClientInvocation invocation; 617 if (myId != null) { 618 invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myId); 619 } else if (myConditional) { 620 invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myResourceType, getParamMap()); 621 } else { 622 invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), mySearchUrl); 623 } 624 OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler(); 625 Map<String, List<String>> params = new HashMap<String, List<String>>(); 626 return invoke(params, binding, invocation); 627 } 628 629 @Override 630 public IDeleteTyped resource(IBaseResource theResource) { 631 Validate.notNull(theResource, "theResource can not be null"); 632 IIdType id = theResource.getIdElement(); 633 Validate.notNull(id, "theResource.getIdElement() can not be null"); 634 if (!id.hasResourceType() || !id.hasIdPart()) { 635 throw new IllegalArgumentException("theResource.getId() must contain a resource type and logical ID at a minimum (e.g. Patient/1234), found: " + id.getValue()); 636 } 637 myId = id; 638 return this; 639 } 640 641 @Override 642 public IDeleteTyped resourceById(IIdType theId) { 643 Validate.notNull(theId, "theId can not be null"); 644 if (!theId.hasResourceType() || !theId.hasIdPart()) { 645 throw new IllegalArgumentException("theId must contain a resource type and logical ID at a minimum (e.g. Patient/1234)found: " + theId.getValue()); 646 } 647 myId = theId; 648 return this; 649 } 650 651 @Override 652 public IDeleteTyped resourceById(String theResourceType, String theLogicalId) { 653 Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); 654 if (myContext.getResourceDefinition(theResourceType) == null) { 655 throw new IllegalArgumentException("Unknown resource type"); 656 } 657 Validate.notBlank(theLogicalId, "theLogicalId can not be blank/null"); 658 if (theLogicalId.contains("/")) { 659 throw new IllegalArgumentException("LogicalId can not contain '/' (should only be the logical ID portion, not a qualified ID)"); 660 } 661 myId = new IdDt(theResourceType, theLogicalId); 662 return this; 663 } 664 665 @Override 666 public IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType) { 667 Validate.notNull(theResourceType, "theResourceType can not be null"); 668 myConditional = true; 669 myResourceType = myContext.getResourceDefinition(theResourceType).getName(); 670 return this; 671 } 672 673 @Override 674 public IDeleteWithQuery resourceConditionalByType(String theResourceType) { 675 Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); 676 if (myContext.getResourceDefinition(theResourceType) == null) { 677 throw new IllegalArgumentException("Unknown resource type: " + theResourceType); 678 } 679 myResourceType = theResourceType; 680 myConditional = true; 681 return this; 682 } 683 684 @Override 685 public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) { 686 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 687 return this; 688 } 689 690 } 691 692 @SuppressWarnings({"rawtypes", "unchecked"}) 693 private class FetchConformanceInternal extends BaseClientExecutable implements IFetchConformanceUntyped, IFetchConformanceTyped { 694 private RuntimeResourceDefinition myType; 695 696 @Override 697 public Object execute() { 698 ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass()); 699 FhirContext fhirContext = getFhirContext(); 700 HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(fhirContext); 701 return super.invoke(null, binding, invocation); 702 } 703 704 @Override 705 public <T extends IBaseConformance> IFetchConformanceTyped<T> ofType(Class<T> theResourceType) { 706 Validate.notNull(theResourceType, "theResourceType must not be null"); 707 myType = myContext.getResourceDefinition(theResourceType); 708 if (myType == null) { 709 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); 710 } 711 return this; 712 } 713 714 } 715 716 @SuppressWarnings({"unchecked", "rawtypes"}) 717 private class GetPageInternal extends BaseClientExecutable<IGetPageTyped<Object>, Object> implements IGetPageTyped<Object> { 718 719 private Class<? extends IBaseBundle> myBundleType; 720 private String myUrl; 721 722 public GetPageInternal(String theUrl, Class<? extends IBaseBundle> theBundleType) { 723 myUrl = theUrl; 724 myBundleType = theBundleType; 725 } 726 727 @Override 728 public Object execute() { 729 IClientResponseHandler binding; 730 binding = new ResourceResponseHandler(myBundleType, getPreferResponseTypes()); 731 HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl); 732 733 Map<String, List<String>> params = null; 734 return invoke(params, binding, invocation); 735 } 736 737 } 738 739 @SuppressWarnings("rawtypes") 740 private class HistoryInternal extends BaseClientExecutable implements IHistory, IHistoryUntyped, IHistoryTyped { 741 742 private Integer myCount; 743 private IIdType myId; 744 private Class<? extends IBaseBundle> myReturnType; 745 private IPrimitiveType mySince; 746 private Class<? extends IBaseResource> myType; 747 private DateRangeParam myAt; 748 749 @SuppressWarnings("unchecked") 750 @Override 751 public IHistoryTyped andReturnBundle(Class theType) { 752 return returnBundle(theType); 753 } 754 755 @Override 756 public IHistoryTyped returnBundle(Class theType) { 757 Validate.notNull(theType, "theType must not be null on method andReturnBundle(Class)"); 758 myReturnType = theType; 759 return this; 760 } 761 762 @Override 763 public IHistoryTyped at(DateRangeParam theDateRangeParam) { 764 myAt = theDateRangeParam; 765 return this; 766 } 767 768 @Override 769 public IHistoryTyped count(Integer theCount) { 770 myCount = theCount; 771 return this; 772 } 773 774 @SuppressWarnings("unchecked") 775 @Override 776 public Object execute() { 777 String resourceName; 778 String id; 779 if (myType != null) { 780 resourceName = myContext.getResourceDefinition(myType).getName(); 781 id = null; 782 } else if (myId != null) { 783 resourceName = myId.getResourceType(); 784 id = myId.getIdPart(); 785 } else { 786 resourceName = null; 787 id = null; 788 } 789 790 HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount, myAt); 791 792 IClientResponseHandler handler; 793 handler = new ResourceResponseHandler(myReturnType, getPreferResponseTypes(myType)); 794 795 return invoke(null, handler, invocation); 796 } 797 798 @Override 799 public IHistoryUntyped onInstance(IIdType theId) { 800 if (!theId.hasResourceType()) { 801 throw new IllegalArgumentException("Resource ID does not have a resource type: " + theId.getValue()); 802 } 803 myId = theId; 804 return this; 805 } 806 807 @Override 808 public IHistoryUntyped onServer() { 809 return this; 810 } 811 812 @Override 813 public IHistoryUntyped onType(Class<? extends IBaseResource> theResourceType) { 814 myType = theResourceType; 815 return this; 816 } 817 818 @Override 819 public IHistoryTyped since(Date theCutoff) { 820 if (theCutoff != null) { 821 mySince = new InstantDt(theCutoff); 822 } else { 823 mySince = null; 824 } 825 return this; 826 } 827 828 @Override 829 public IHistoryTyped since(IPrimitiveType theCutoff) { 830 mySince = theCutoff; 831 return this; 832 } 833 834 } 835 836 @SuppressWarnings({"unchecked", "rawtypes"}) 837 private final class LoadPageInternal implements IGetPage, IGetPageUntyped { 838 839 private static final String PREV = "prev"; 840 private static final String PREVIOUS = "previous"; 841 private String myPageUrl; 842 843 @Override 844 public <T extends IBaseBundle> IGetPageTyped andReturnBundle(Class<T> theBundleType) { 845 Validate.notNull(theBundleType, "theBundleType must not be null"); 846 return new GetPageInternal(myPageUrl, theBundleType); 847 } 848 849 @Override 850 public IGetPageUntyped byUrl(String thePageUrl) { 851 if (isBlank(thePageUrl)) { 852 throw new IllegalArgumentException("thePagingUrl must not be blank or null"); 853 } 854 myPageUrl = thePageUrl; 855 return this; 856 } 857 858 @Override 859 public <T extends IBaseBundle> IGetPageTyped<T> next(T theBundle) { 860 return nextOrPrevious("next", theBundle); 861 } 862 863 private <T extends IBaseBundle> IGetPageTyped<T> nextOrPrevious(String theWantRel, T theBundle) { 864 RuntimeResourceDefinition def = myContext.getResourceDefinition(theBundle); 865 List<IBase> links = def.getChildByName("link").getAccessor().getValues(theBundle); 866 if (links == null || links.isEmpty()) { 867 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "noPagingLinkFoundInBundle", theWantRel)); 868 } 869 for (IBase nextLink : links) { 870 BaseRuntimeElementCompositeDefinition linkDef = (BaseRuntimeElementCompositeDefinition) myContext.getElementDefinition(nextLink.getClass()); 871 List<IBase> rel = linkDef.getChildByName("relation").getAccessor().getValues(nextLink); 872 if (rel == null || rel.isEmpty()) { 873 continue; 874 } 875 String relation = ((IPrimitiveType<?>) rel.get(0)).getValueAsString(); 876 if (theWantRel.equals(relation) || (PREVIOUS.equals(theWantRel) && PREV.equals(relation))) { 877 List<IBase> urls = linkDef.getChildByName("url").getAccessor().getValues(nextLink); 878 if (urls == null || urls.isEmpty()) { 879 continue; 880 } 881 String url = ((IPrimitiveType<?>) urls.get(0)).getValueAsString(); 882 if (isBlank(url)) { 883 continue; 884 } 885 return (IGetPageTyped<T>) byUrl(url).andReturnBundle(theBundle.getClass()); 886 } 887 } 888 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "noPagingLinkFoundInBundle", theWantRel)); 889 } 890 891 @Override 892 public <T extends IBaseBundle> IGetPageTyped<T> previous(T theBundle) { 893 return nextOrPrevious(PREVIOUS, theBundle); 894 } 895 896 } 897 898 @SuppressWarnings("rawtypes") 899 private class MetaInternal extends BaseClientExecutable implements IMeta, IMetaAddOrDeleteUnsourced, IMetaGetUnsourced, IMetaAddOrDeleteSourced { 900 901 private IIdType myId; 902 private IBaseMetaType myMeta; 903 private Class<? extends IBaseMetaType> myMetaType; 904 private String myOnType; 905 private MetaOperation myOperation; 906 907 @Override 908 public IMetaAddOrDeleteUnsourced add() { 909 myOperation = MetaOperation.ADD; 910 return this; 911 } 912 913 @Override 914 public IMetaAddOrDeleteUnsourced delete() { 915 myOperation = MetaOperation.DELETE; 916 return this; 917 } 918 919 @SuppressWarnings("unchecked") 920 @Override 921 public Object execute() { 922 923 BaseHttpClientInvocation invocation = null; 924 925 IBaseParameters parameters = ParametersUtil.newInstance(myContext); 926 switch (myOperation) { 927 case ADD: 928 ParametersUtil.addParameterToParameters(myContext, parameters, "meta", myMeta); 929 invocation = OperationMethodBinding.createOperationInvocation(myContext, myId.getResourceType(), myId.getIdPart(), null, "$meta-add", parameters, false); 930 break; 931 case DELETE: 932 ParametersUtil.addParameterToParameters(myContext, parameters, "meta", myMeta); 933 invocation = OperationMethodBinding.createOperationInvocation(myContext, myId.getResourceType(), myId.getIdPart(), null, "$meta-delete", parameters, false); 934 break; 935 case GET: 936 if (myId != null) { 937 invocation = OperationMethodBinding.createOperationInvocation(myContext, myOnType, myId.getIdPart(), null, "$meta", parameters, true); 938 } else if (myOnType != null) { 939 invocation = OperationMethodBinding.createOperationInvocation(myContext, myOnType, null, null, "$meta", parameters, true); 940 } else { 941 invocation = OperationMethodBinding.createOperationInvocation(myContext, null, null, null, "$meta", parameters, true); 942 } 943 break; 944 } 945 946 // Should not happen 947 if (invocation == null) { 948 throw new IllegalStateException(); 949 } 950 951 IClientResponseHandler handler; 952 handler = new MetaParametersResponseHandler(myMetaType); 953 return invoke(null, handler, invocation); 954 } 955 956 @Override 957 public IClientExecutable fromResource(IIdType theId) { 958 setIdInternal(theId); 959 return this; 960 } 961 962 @Override 963 public IClientExecutable fromServer() { 964 return this; 965 } 966 967 @Override 968 public IClientExecutable fromType(String theResourceName) { 969 Validate.notBlank(theResourceName, "theResourceName must not be blank"); 970 myOnType = theResourceName; 971 return this; 972 } 973 974 @SuppressWarnings("unchecked") 975 @Override 976 public <T extends IBaseMetaType> IMetaGetUnsourced<T> get(Class<T> theType) { 977 myMetaType = theType; 978 myOperation = MetaOperation.GET; 979 return this; 980 } 981 982 @SuppressWarnings("unchecked") 983 @Override 984 public <T extends IBaseMetaType> IClientExecutable<IClientExecutable<?, T>, T> meta(T theMeta) { 985 Validate.notNull(theMeta, "theMeta must not be null"); 986 myMeta = theMeta; 987 myMetaType = myMeta.getClass(); 988 return this; 989 } 990 991 @Override 992 public IMetaAddOrDeleteSourced onResource(IIdType theId) { 993 setIdInternal(theId); 994 return this; 995 } 996 997 private void setIdInternal(IIdType theId) { 998 Validate.notBlank(theId.getResourceType(), "theId must contain a resource type"); 999 Validate.notBlank(theId.getIdPart(), "theId must contain an ID part"); 1000 myOnType = theId.getResourceType(); 1001 myId = theId; 1002 } 1003 1004 } 1005 1006 private final class MetaParametersResponseHandler<T extends IBaseMetaType> implements IClientResponseHandler<T> { 1007 1008 private Class<T> myType; 1009 1010 public MetaParametersResponseHandler(Class<T> theMetaType) { 1011 myType = theMetaType; 1012 } 1013 1014 @SuppressWarnings("unchecked") 1015 @Override 1016 public T invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 1017 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 1018 if (respType == null) { 1019 throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseInputStream); 1020 } 1021 IParser parser = respType.newParser(myContext); 1022 RuntimeResourceDefinition type = myContext.getResourceDefinition("Parameters"); 1023 IBaseResource retVal = parser.parseResource(type.getImplementingClass(), theResponseInputStream); 1024 1025 BaseRuntimeChildDefinition paramChild = type.getChildByName("parameter"); 1026 BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter"); 1027 List<IBase> parameter = paramChild.getAccessor().getValues(retVal); 1028 if (parameter == null || parameter.isEmpty()) { 1029 return (T) myContext.getElementDefinition(myType).newInstance(); 1030 } 1031 IBase param = parameter.get(0); 1032 1033 List<IBase> meta = paramChildElem.getChildByName("value[x]").getAccessor().getValues(param); 1034 if (meta.isEmpty()) { 1035 return (T) myContext.getElementDefinition(myType).newInstance(); 1036 } 1037 return (T) meta.get(0); 1038 1039 } 1040 } 1041 1042 @SuppressWarnings("rawtypes") 1043 private class OperationInternal extends BaseClientExecutable 1044 implements IOperation, IOperationUnnamed, IOperationUntyped, IOperationUntypedWithInput, IOperationUntypedWithInputAndPartialOutput, IOperationProcessMsg, IOperationProcessMsgMode { 1045 1046 private IIdType myId; 1047 private Boolean myIsAsync; 1048 private IBaseBundle myMsgBundle; 1049 private String myOperationName; 1050 private IBaseParameters myParameters; 1051 private RuntimeResourceDefinition myParametersDef; 1052 private String myResponseUrl; 1053 private Class myReturnResourceType; 1054 private Class<? extends IBaseResource> myType; 1055 private boolean myUseHttpGet; 1056 private boolean myReturnMethodOutcome; 1057 1058 @SuppressWarnings("unchecked") 1059 private void addParam(String theName, IBase theValue) { 1060 BaseRuntimeChildDefinition parameterChild = myParametersDef.getChildByName("parameter"); 1061 BaseRuntimeElementCompositeDefinition<?> parameterElem = (BaseRuntimeElementCompositeDefinition<?>) parameterChild.getChildByName("parameter"); 1062 1063 IBase parameter = parameterElem.newInstance(); 1064 parameterChild.getMutator().addValue(myParameters, parameter); 1065 1066 IPrimitiveType<String> name = (IPrimitiveType<String>) myContext.getElementDefinition("string").newInstance(); 1067 name.setValue(theName); 1068 parameterElem.getChildByName("name").getMutator().setValue(parameter, name); 1069 1070 if (theValue instanceof IBaseDatatype) { 1071 BaseRuntimeElementDefinition<?> datatypeDef = myContext.getElementDefinition(theValue.getClass()); 1072 if (datatypeDef instanceof IRuntimeDatatypeDefinition) { 1073 Class<? extends IBaseDatatype> profileOf = ((IRuntimeDatatypeDefinition) datatypeDef).getProfileOf(); 1074 if (profileOf != null) { 1075 datatypeDef = myContext.getElementDefinition(profileOf); 1076 } 1077 } 1078 String childElementName = "value" + StringUtils.capitalize(datatypeDef.getName()); 1079 BaseRuntimeChildDefinition childByName = parameterElem.getChildByName(childElementName); 1080 childByName.getMutator().setValue(parameter, theValue); 1081 } else if (theValue instanceof IBaseResource) { 1082 parameterElem.getChildByName("resource").getMutator().setValue(parameter, theValue); 1083 } else { 1084 throw new IllegalArgumentException("Don't know how to handle parameter of type " + theValue.getClass()); 1085 } 1086 } 1087 1088 private void addParam(String theName, IQueryParameterType theValue) { 1089 IPrimitiveType<?> stringType = ParametersUtil.createString(myContext, theValue.getValueAsQueryToken(myContext)); 1090 addParam(theName, stringType); 1091 } 1092 1093 @Override 1094 public IOperationUntypedWithInputAndPartialOutput andParameter(String theName, IBase theValue) { 1095 Validate.notEmpty(theName, "theName must not be null"); 1096 Validate.notNull(theValue, "theValue must not be null"); 1097 addParam(theName, theValue); 1098 return this; 1099 } 1100 1101 @Override 1102 public IOperationUntypedWithInputAndPartialOutput andSearchParameter(String theName, IQueryParameterType theValue) { 1103 addParam(theName, theValue); 1104 1105 return this; 1106 } 1107 1108 @Override 1109 public IOperationProcessMsgMode asynchronous(Class theResponseClass) { 1110 myIsAsync = true; 1111 Validate.notNull(theResponseClass, "theReturnType must not be null"); 1112 Validate.isTrue(IBaseResource.class.isAssignableFrom(theResponseClass), "theReturnType must be a class which extends from IBaseResource"); 1113 myReturnResourceType = theResponseClass; 1114 return this; 1115 } 1116 1117 @SuppressWarnings("unchecked") 1118 @Override 1119 public Object execute() { 1120 if (myOperationName != null && myOperationName.equals(Constants.EXTOP_PROCESS_MESSAGE) && myMsgBundle != null) { 1121 Map<String, List<String>> urlParams = new LinkedHashMap<String, List<String>>(); 1122 // Set Url parameter Async and Response-Url 1123 if (myIsAsync != null) { 1124 urlParams.put(Constants.PARAM_ASYNC, Arrays.asList(String.valueOf(myIsAsync))); 1125 } 1126 1127 if (myResponseUrl != null && isNotBlank(myResponseUrl)) { 1128 urlParams.put(Constants.PARAM_RESPONSE_URL, Arrays.asList(String.valueOf(myResponseUrl))); 1129 } 1130 // If is $process-message operation 1131 BaseHttpClientInvocation invocation = OperationMethodBinding.createProcessMsgInvocation(myContext, myOperationName, myMsgBundle, urlParams); 1132 1133 ResourceResponseHandler handler = new ResourceResponseHandler(); 1134 handler.setPreferResponseTypes(getPreferResponseTypes(myType)); 1135 1136 Object retVal = invoke(null, handler, invocation); 1137 return retVal; 1138 } 1139 1140 String resourceName; 1141 String id; 1142 String version; 1143 if (myType != null) { 1144 resourceName = myContext.getResourceDefinition(myType).getName(); 1145 id = null; 1146 version = null; 1147 } else if (myId != null) { 1148 resourceName = myId.getResourceType(); 1149 Validate.notBlank(defaultString(resourceName), "Can not invoke operation \"$%s\" on instance \"%s\" - No resource type specified", myOperationName, myId.getValue()); 1150 id = myId.getIdPart(); 1151 version = myId.getVersionIdPart(); 1152 } else { 1153 resourceName = null; 1154 id = null; 1155 version = null; 1156 } 1157 1158 BaseHttpClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, version, myOperationName, myParameters, myUseHttpGet); 1159 1160 if (myReturnResourceType != null) { 1161 ResourceResponseHandler handler; 1162 handler = new ResourceResponseHandler(myReturnResourceType); 1163 Object retVal = invoke(null, handler, invocation); 1164 return retVal; 1165 } 1166 IClientResponseHandler handler = new ResourceOrBinaryResponseHandler() 1167 .setPreferResponseTypes(getPreferResponseTypes(myType)); 1168 1169 if (myReturnMethodOutcome) { 1170 handler = new MethodOutcomeResponseHandler(handler); 1171 } 1172 1173 Object retVal = invoke(null, handler, invocation); 1174 1175 if (myReturnMethodOutcome) { 1176 return retVal; 1177 } 1178 1179 if (myContext.getResourceDefinition((IBaseResource) retVal).getName().equals("Parameters")) { 1180 return retVal; 1181 } 1182 RuntimeResourceDefinition def = myContext.getResourceDefinition("Parameters"); 1183 IBaseResource parameters = def.newInstance(); 1184 1185 BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter"); 1186 BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter"); 1187 IBase parameter = paramChildElem.newInstance(); 1188 paramChild.getMutator().addValue(parameters, parameter); 1189 1190 BaseRuntimeChildDefinition resourceElem = paramChildElem.getChildByName("resource"); 1191 resourceElem.getMutator().addValue(parameter, (IBase) retVal); 1192 1193 return parameters; 1194 } 1195 1196 @Override 1197 public IOperationUntyped named(String theName) { 1198 Validate.notBlank(theName, "theName can not be null"); 1199 myOperationName = theName; 1200 return this; 1201 } 1202 1203 @Override 1204 public IOperationUnnamed onInstance(IIdType theId) { 1205 myId = theId.toVersionless(); 1206 return this; 1207 } 1208 1209 @Override 1210 public IOperationUnnamed onInstanceVersion(IIdType theId) { 1211 myId = theId; 1212 return this; 1213 } 1214 1215 @Override 1216 public IOperationUnnamed onServer() { 1217 return this; 1218 } 1219 1220 @Override 1221 public IOperationUnnamed onType(Class<? extends IBaseResource> theResourceType) { 1222 myType = theResourceType; 1223 return this; 1224 } 1225 1226 @Override 1227 public IOperationProcessMsg processMessage() { 1228 myOperationName = Constants.EXTOP_PROCESS_MESSAGE; 1229 return this; 1230 } 1231 1232 @Override 1233 public IOperationUntypedWithInput returnResourceType(Class theReturnType) { 1234 Validate.notNull(theReturnType, "theReturnType must not be null"); 1235 Validate.isTrue(IBaseResource.class.isAssignableFrom(theReturnType), "theReturnType must be a class which extends from IBaseResource"); 1236 myReturnResourceType = theReturnType; 1237 return this; 1238 } 1239 1240 @Override 1241 public IOperationUntypedWithInput returnMethodOutcome() { 1242 myReturnMethodOutcome = true; 1243 return this; 1244 } 1245 1246 @SuppressWarnings("unchecked") 1247 @Override 1248 public IOperationProcessMsgMode setMessageBundle(IBaseBundle theMsgBundle) { 1249 1250 Validate.notNull(theMsgBundle, "theMsgBundle must not be null"); 1251 /* 1252 * Validate.isTrue(theMsgBundle.getType().getValueAsEnum() == BundleTypeEnum.MESSAGE); 1253 * Validate.isTrue(theMsgBundle.getEntries().size() > 0); 1254 * Validate.notNull(theMsgBundle.getEntries().get(0).getResource(), "Message Bundle first entry must be a MessageHeader resource"); 1255 * Validate.isTrue(theMsgBundle.getEntries().get(0).getResource().getResourceName().equals("MessageHeader"), "Message Bundle first entry must be a MessageHeader resource"); 1256 */ 1257 myMsgBundle = theMsgBundle; 1258 return this; 1259 } 1260 1261 @Override 1262 public IOperationProcessMsg setResponseUrlParam(String responseUrl) { 1263 Validate.notEmpty(responseUrl, "responseUrl must not be null"); 1264 Validate.matchesPattern(responseUrl, "^(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "responseUrl must be a valid URL"); 1265 myResponseUrl = responseUrl; 1266 return this; 1267 } 1268 1269 @Override 1270 public IOperationProcessMsgMode synchronous(Class theResponseClass) { 1271 myIsAsync = false; 1272 Validate.notNull(theResponseClass, "theReturnType must not be null"); 1273 Validate.isTrue(IBaseResource.class.isAssignableFrom(theResponseClass), "theReturnType must be a class which extends from IBaseResource"); 1274 myReturnResourceType = theResponseClass; 1275 return this; 1276 } 1277 1278 @Override 1279 public IOperationUntypedWithInput useHttpGet() { 1280 myUseHttpGet = true; 1281 return this; 1282 } 1283 1284 @SuppressWarnings("unchecked") 1285 @Override 1286 public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withNoParameters(Class<T> theOutputParameterType) { 1287 Validate.notNull(theOutputParameterType, "theOutputParameterType may not be null"); 1288 RuntimeResourceDefinition def = myContext.getResourceDefinition(theOutputParameterType); 1289 if (def == null) { 1290 throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type: " + theOutputParameterType.getName()); 1291 } 1292 if (!"Parameters".equals(def.getName())) { 1293 throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type for a resource named " + "Parameters" + " - " + theOutputParameterType.getName() 1294 + " is a resource named: " + def.getName()); 1295 } 1296 myParameters = (IBaseParameters) def.newInstance(); 1297 return this; 1298 } 1299 1300 @SuppressWarnings("unchecked") 1301 @Override 1302 public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withParameter(Class<T> theParameterType, String theName, IBase theValue) { 1303 Validate.notNull(theParameterType, "theParameterType must not be null"); 1304 Validate.notEmpty(theName, "theName must not be null"); 1305 Validate.notNull(theValue, "theValue must not be null"); 1306 1307 myParametersDef = myContext.getResourceDefinition(theParameterType); 1308 myParameters = (IBaseParameters) myParametersDef.newInstance(); 1309 1310 addParam(theName, theValue); 1311 1312 return this; 1313 } 1314 1315 @SuppressWarnings({"unchecked"}) 1316 @Override 1317 public IOperationUntypedWithInputAndPartialOutput withParameters(IBaseParameters theParameters) { 1318 Validate.notNull(theParameters, "theParameters can not be null"); 1319 myParameters = theParameters; 1320 myParametersDef = myContext.getResourceDefinition(theParameters.getClass()); 1321 return this; 1322 } 1323 1324 @SuppressWarnings("unchecked") 1325 @Override 1326 public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withSearchParameter(Class<T> theParameterType, String theName, IQueryParameterType theValue) { 1327 Validate.notNull(theParameterType, "theParameterType must not be null"); 1328 Validate.notEmpty(theName, "theName must not be null"); 1329 Validate.notNull(theValue, "theValue must not be null"); 1330 1331 myParametersDef = myContext.getResourceDefinition(theParameterType); 1332 myParameters = (IBaseParameters) myParametersDef.newInstance(); 1333 1334 addParam(theName, theValue); 1335 1336 return this; 1337 } 1338 1339 } 1340 1341 1342 private final class MethodOutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> { 1343 private final IClientResponseHandler<? extends IBaseResource> myWrap; 1344 1345 private MethodOutcomeResponseHandler(IClientResponseHandler<? extends IBaseResource> theWrap) { 1346 myWrap = theWrap; 1347 } 1348 1349 @Override 1350 public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException { 1351 IBaseResource response = myWrap.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders); 1352 1353 MethodOutcome retVal = new MethodOutcome(); 1354 retVal.setResource(response); 1355 retVal.setCreatedUsingStatusCode(theResponseStatusCode); 1356 retVal.setResponseHeaders(theHeaders); 1357 return retVal; 1358 } 1359 } 1360 1361 private final class OperationOutcomeResponseHandler implements IClientResponseHandler<IBaseOperationOutcome> { 1362 1363 @Override 1364 public IBaseOperationOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) 1365 throws BaseServerResponseException { 1366 EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); 1367 if (respType == null) { 1368 return null; 1369 } 1370 IParser parser = respType.newParser(myContext); 1371 IBaseOperationOutcome retVal; 1372 try { 1373 // TODO: handle if something else than OO comes back 1374 retVal = (IBaseOperationOutcome) parser.parseResource(theResponseInputStream); 1375 } catch (DataFormatException e) { 1376 ourLog.warn("Failed to parse OperationOutcome response", e); 1377 return null; 1378 } 1379 MethodUtil.parseClientRequestResourceHeaders(null, theHeaders, retVal); 1380 1381 return retVal; 1382 } 1383 } 1384 1385 private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> { 1386 private PreferReturnEnum myPrefer; 1387 1388 private OutcomeResponseHandler() { 1389 super(); 1390 } 1391 1392 private OutcomeResponseHandler(PreferReturnEnum thePrefer) { 1393 this(); 1394 myPrefer = thePrefer; 1395 } 1396 1397 @Override 1398 public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 1399 MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders); 1400 response.setCreatedUsingStatusCode(theResponseStatusCode); 1401 1402 if (myPrefer == PreferReturnEnum.REPRESENTATION) { 1403 if (response.getResource() == null) { 1404 if (response.getId() != null && isNotBlank(response.getId().getValue()) && response.getId().hasBaseUrl()) { 1405 ourLog.info("Server did not return resource for Prefer-representation, going to fetch: {}", response.getId().getValue()); 1406 IBaseResource resource = read().resource(response.getId().getResourceType()).withUrl(response.getId()).execute(); 1407 response.setResource(resource); 1408 } 1409 } 1410 } 1411 1412 response.setResponseHeaders(theHeaders); 1413 1414 return response; 1415 } 1416 } 1417 1418 private class PatchInternal extends BaseSearch<IPatchExecutable, IPatchWithQueryTyped, MethodOutcome> implements IPatch, IPatchWithBody, IPatchExecutable, IPatchWithQuery, IPatchWithQueryTyped { 1419 1420 private boolean myConditional; 1421 private IIdType myId; 1422 private String myPatchBody; 1423 private PatchTypeEnum myPatchType; 1424 private PreferReturnEnum myPrefer; 1425 private String myResourceType; 1426 private String mySearchUrl; 1427 1428 @Override 1429 public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) { 1430 Validate.notNull(theClass, "theClass must not be null"); 1431 String resourceType = myContext.getResourceDefinition(theClass).getName(); 1432 return conditional(resourceType); 1433 } 1434 1435 @Override 1436 public IPatchWithQuery conditional(String theResourceType) { 1437 Validate.notBlank(theResourceType, "theResourceType must not be null"); 1438 myResourceType = theResourceType; 1439 myConditional = true; 1440 return this; 1441 } 1442 1443 // TODO: This is not longer used.. Deprecate it or just remove it? 1444 @Override 1445 public IPatchWithBody conditionalByUrl(String theSearchUrl) { 1446 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 1447 return this; 1448 } 1449 1450 @Override 1451 public MethodOutcome execute() { 1452 1453 if (myPatchType == null) { 1454 throw new InvalidRequestException("No patch type supplied, cannot invoke server"); 1455 } 1456 if (myPatchBody == null) { 1457 throw new InvalidRequestException("No patch body supplied, cannot invoke server"); 1458 } 1459 1460 BaseHttpClientInvocation invocation; 1461 if (isNotBlank(mySearchUrl)) { 1462 invocation = MethodUtil.createPatchInvocation(myContext, mySearchUrl, myPatchType, myPatchBody); 1463 } else if (myConditional) { 1464 invocation = MethodUtil.createPatchInvocation(myContext, myPatchType, myPatchBody, myResourceType, getParamMap()); 1465 } else { 1466 if (myId == null || myId.hasIdPart() == false) { 1467 throw new InvalidRequestException("No ID supplied for resource to patch, can not invoke server"); 1468 } 1469 invocation = MethodUtil.createPatchInvocation(myContext, myId, myPatchType, myPatchBody); 1470 } 1471 1472 addPreferHeader(myPrefer, invocation); 1473 1474 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 1475 1476 Map<String, List<String>> params = new HashMap<>(); 1477 return invoke(params, binding, invocation); 1478 1479 } 1480 1481 @Override 1482 public IPatchExecutable prefer(PreferReturnEnum theReturn) { 1483 myPrefer = theReturn; 1484 return this; 1485 } 1486 1487 @Override 1488 public IPatchWithBody withBody(String thePatchBody) { 1489 Validate.notBlank(thePatchBody, "thePatchBody must not be blank"); 1490 1491 myPatchBody = thePatchBody; 1492 1493 EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(thePatchBody); 1494 if (encoding == EncodingEnum.XML) { 1495 myPatchType = PatchTypeEnum.XML_PATCH; 1496 } else if (encoding == EncodingEnum.JSON) { 1497 myPatchType = PatchTypeEnum.JSON_PATCH; 1498 } else { 1499 throw new IllegalArgumentException("Unable to determine encoding of patch"); 1500 } 1501 1502 return this; 1503 } 1504 1505 @Override 1506 public IPatchExecutable withId(IIdType theId) { 1507 if (theId == null) { 1508 throw new NullPointerException("theId can not be null"); 1509 } 1510 if (theId.hasIdPart() == false) { 1511 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue()); 1512 } 1513 myId = theId; 1514 return this; 1515 } 1516 1517 @Override 1518 public IPatchExecutable withId(String theId) { 1519 if (theId == null) { 1520 throw new NullPointerException("theId can not be null"); 1521 } 1522 if (isBlank(theId)) { 1523 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId); 1524 } 1525 myId = new IdDt(theId); 1526 return this; 1527 } 1528 1529 } 1530 1531 @SuppressWarnings({"rawtypes", "unchecked"}) 1532 private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable { 1533 private IIdType myId; 1534 private String myIfVersionMatches; 1535 private ICallable myNotModifiedHandler; 1536 private RuntimeResourceDefinition myType; 1537 1538 @Override 1539 public Object execute() {// AAA 1540 if (myId.hasVersionIdPart()) { 1541 return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues); 1542 } 1543 return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues); 1544 } 1545 1546 @Override 1547 public IReadIfNoneMatch ifVersionMatches(String theVersion) { 1548 myIfVersionMatches = theVersion; 1549 return new IReadIfNoneMatch() { 1550 1551 @Override 1552 public IReadExecutable returnNull() { 1553 myNotModifiedHandler = new ICallable() { 1554 @Override 1555 public Object call() { 1556 return null; 1557 } 1558 }; 1559 return ReadInternal.this; 1560 } 1561 1562 @Override 1563 public IReadExecutable returnResource(final IBaseResource theInstance) { 1564 myNotModifiedHandler = new ICallable() { 1565 @Override 1566 public Object call() { 1567 return theInstance; 1568 } 1569 }; 1570 return ReadInternal.this; 1571 } 1572 1573 @Override 1574 public IReadExecutable throwNotModifiedException() { 1575 myNotModifiedHandler = null; 1576 return ReadInternal.this; 1577 } 1578 }; 1579 } 1580 1581 private void processUrl() { 1582 String resourceType = myId.getResourceType(); 1583 if (isBlank(resourceType)) { 1584 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId)); 1585 } 1586 myType = myContext.getResourceDefinition(resourceType); 1587 if (myType == null) { 1588 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId)); 1589 } 1590 } 1591 1592 @Override 1593 public <T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType) { 1594 Validate.notNull(theResourceType, "theResourceType must not be null"); 1595 myType = myContext.getResourceDefinition(theResourceType); 1596 if (myType == null) { 1597 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); 1598 } 1599 return this; 1600 } 1601 1602 @Override 1603 public IReadTyped<IBaseResource> resource(String theResourceAsText) { 1604 Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText"); 1605 myType = myContext.getResourceDefinition(theResourceAsText); 1606 if (myType == null) { 1607 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText)); 1608 } 1609 return this; 1610 } 1611 1612 @Override 1613 public IReadExecutable withId(IIdType theId) { 1614 Validate.notNull(theId, "The ID can not be null"); 1615 Validate.notBlank(theId.getIdPart(), "The ID can not be blank"); 1616 myId = theId.toUnqualified(); 1617 return this; 1618 } 1619 1620 @Override 1621 public IReadExecutable withId(Long theId) { 1622 Validate.notNull(theId, "The ID can not be null"); 1623 myId = new IdDt(myType.getName(), theId); 1624 return this; 1625 } 1626 1627 @Override 1628 public IReadExecutable withId(String theId) { 1629 Validate.notBlank(theId, "The ID can not be blank"); 1630 if (theId.indexOf('/') == -1) { 1631 myId = new IdDt(myType.getName(), theId); 1632 } else { 1633 myId = new IdDt(theId); 1634 } 1635 return this; 1636 } 1637 1638 @Override 1639 public IReadExecutable withIdAndVersion(String theId, String theVersion) { 1640 Validate.notBlank(theId, "The ID can not be blank"); 1641 myId = new IdDt(myType.getName(), theId, theVersion); 1642 return this; 1643 } 1644 1645 @Override 1646 public IReadExecutable withUrl(IIdType theUrl) { 1647 Validate.notNull(theUrl, "theUrl can not be null"); 1648 myId = theUrl; 1649 processUrl(); 1650 return this; 1651 } 1652 1653 @Override 1654 public IReadExecutable withUrl(String theUrl) { 1655 myId = new IdDt(theUrl); 1656 processUrl(); 1657 return this; 1658 } 1659 1660 } 1661 1662 private final class ResourceListResponseHandler implements IClientResponseHandler<List<IBaseResource>> { 1663 1664 @SuppressWarnings("unchecked") 1665 @Override 1666 public List<IBaseResource> invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) 1667 throws BaseServerResponseException { 1668 Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass(); 1669 ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<>((Class<IBaseResource>) bundleType); 1670 IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders); 1671 IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); 1672 bundleFactory.initializeWithBundleResource(response); 1673 return bundleFactory.toListOfResources(); 1674 } 1675 } 1676 1677 @SuppressWarnings({"rawtypes", "unchecked"}) 1678 private class SearchInternal<OUTPUT> extends BaseSearch<IQuery<OUTPUT>, IQuery<OUTPUT>, OUTPUT> implements IQuery<OUTPUT>, IUntypedQuery<IQuery<OUTPUT>> { 1679 1680 private String myCompartmentName; 1681 private List<Include> myInclude = new ArrayList<>(); 1682 private DateRangeParam myLastUpdated; 1683 private Integer myParamLimit; 1684 private List<Collection<String>> myProfiles = new ArrayList<>(); 1685 private String myResourceId; 1686 private String myResourceName; 1687 private Class<? extends IBaseResource> myResourceType; 1688 private Class<? extends IBaseBundle> myReturnBundleType; 1689 private List<Include> myRevInclude = new ArrayList<>(); 1690 private SearchStyleEnum mySearchStyle; 1691 private String mySearchUrl; 1692 private List<TokenParam> mySecurity = new ArrayList<>(); 1693 private List<SortInternal> mySort = new ArrayList<>(); 1694 private List<TokenParam> myTags = new ArrayList<>(); 1695 private SearchTotalModeEnum myTotalMode; 1696 1697 public SearchInternal() { 1698 myResourceType = null; 1699 myResourceName = null; 1700 mySearchUrl = null; 1701 } 1702 1703 @Override 1704 public IQuery byUrl(String theSearchUrl) { 1705 Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null"); 1706 1707 if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) { 1708 mySearchUrl = theSearchUrl; 1709 int qIndex = mySearchUrl.indexOf('?'); 1710 if (qIndex != -1) { 1711 mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex)); 1712 } 1713 } else { 1714 String searchUrl = theSearchUrl; 1715 if (searchUrl.startsWith("/")) { 1716 searchUrl = searchUrl.substring(1); 1717 } 1718 if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) { 1719 throw new IllegalArgumentException("Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]"); 1720 } 1721 int qIndex = searchUrl.indexOf('?'); 1722 if (qIndex == -1) { 1723 mySearchUrl = getUrlBase() + '/' + searchUrl; 1724 } else { 1725 mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl); 1726 } 1727 } 1728 return this; 1729 } 1730 1731 @Override 1732 public IQuery count(int theLimitTo) { 1733 if (theLimitTo > 0) { 1734 myParamLimit = theLimitTo; 1735 } else { 1736 myParamLimit = null; 1737 } 1738 return this; 1739 } 1740 1741 @Override 1742 public OUTPUT execute() { 1743 1744 Map<String, List<String>> params = getParamMap(); 1745 1746 for (TokenParam next : myTags) { 1747 addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken(myContext)); 1748 } 1749 1750 for (TokenParam next : mySecurity) { 1751 addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken(myContext)); 1752 } 1753 1754 for (Collection<String> profileUris : myProfiles) { 1755 StringBuilder builder = new StringBuilder(); 1756 for (Iterator<String> profileItr = profileUris.iterator(); profileItr.hasNext(); ) { 1757 builder.append(profileItr.next()); 1758 if (profileItr.hasNext()) { 1759 builder.append(','); 1760 } 1761 } 1762 addParam(params, Constants.PARAM_PROFILE, builder.toString()); 1763 } 1764 1765 for (Include next : myInclude) { 1766 if (next.isRecurse()) { 1767 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 1768 addParam(params, Constants.PARAM_INCLUDE_ITERATE, next.getValue()); 1769 } else { 1770 addParam(params, Constants.PARAM_INCLUDE_RECURSE, next.getValue()); 1771 } 1772 } else { 1773 addParam(params, Constants.PARAM_INCLUDE, next.getValue()); 1774 } 1775 } 1776 1777 for (Include next : myRevInclude) { 1778 if (next.isRecurse()) { 1779 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 1780 addParam(params, Constants.PARAM_REVINCLUDE_ITERATE, next.getValue()); 1781 } else { 1782 addParam(params, Constants.PARAM_REVINCLUDE_RECURSE, next.getValue()); 1783 } 1784 } else { 1785 addParam(params, Constants.PARAM_REVINCLUDE, next.getValue()); 1786 } 1787 } 1788 1789 if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) { 1790 SortSpec rootSs = null; 1791 SortSpec lastSs = null; 1792 for (SortInternal next : mySort) { 1793 SortSpec nextSortSpec = new SortSpec(); 1794 nextSortSpec.setParamName(next.getParamValue()); 1795 nextSortSpec.setOrder(next.getDirection()); 1796 if (rootSs == null) { 1797 rootSs = nextSortSpec; 1798 } else { 1799 // FIXME lastSs is null never set 1800 // TODO unused assignment 1801 lastSs.setChain(nextSortSpec); 1802 } 1803 // TODO unused assignment 1804 lastSs = nextSortSpec; 1805 } 1806 if (rootSs != null) { 1807 addParam(params, Constants.PARAM_SORT, SortParameter.createSortStringDstu3(rootSs)); 1808 } 1809 } else { 1810 for (SortInternal next : mySort) { 1811 addParam(params, next.getParamName(), next.getParamValue()); 1812 } 1813 } 1814 1815 if (myParamLimit != null) { 1816 addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit)); 1817 } 1818 1819 if (myLastUpdated != null) { 1820 for (DateParam next : myLastUpdated.getValuesAsQueryTokens()) { 1821 addParam(params, Constants.PARAM_LASTUPDATED, next.getValueAsQueryToken(myContext)); 1822 } 1823 } 1824 1825 if (myTotalMode != null) { 1826 addParam(params, Constants.PARAM_SEARCH_TOTAL_MODE, myTotalMode.getCode()); 1827 } 1828 1829 IClientResponseHandler<? extends IBase> binding; 1830 binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType)); 1831 1832 IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null; 1833 1834 BaseHttpClientInvocation invocation; 1835 if (mySearchUrl != null) { 1836 invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, params); 1837 } else { 1838 invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle); 1839 } 1840 1841 return (OUTPUT) invoke(params, binding, invocation); 1842 1843 } 1844 1845 @Override 1846 public IQuery forAllResources() { 1847 return this; 1848 } 1849 1850 @Override 1851 public IQuery forResource(Class theResourceType) { 1852 setType(theResourceType); 1853 return this; 1854 } 1855 1856 @Override 1857 public IQuery forResource(String theResourceName) { 1858 setType(theResourceName); 1859 return this; 1860 } 1861 1862 @Override 1863 public IQuery include(Include theInclude) { 1864 myInclude.add(theInclude); 1865 return this; 1866 } 1867 1868 @Override 1869 public IQuery lastUpdated(DateRangeParam theLastUpdated) { 1870 myLastUpdated = theLastUpdated; 1871 return this; 1872 } 1873 1874 @Deprecated // override deprecated method 1875 @Override 1876 public IQuery limitTo(int theLimitTo) { 1877 return count(theLimitTo); 1878 } 1879 1880 @Override 1881 public IQuery<OUTPUT> totalMode(SearchTotalModeEnum theSearchTotalModeEnum) { 1882 myTotalMode = theSearchTotalModeEnum; 1883 return this; 1884 } 1885 1886 @Override 1887 public IQuery returnBundle(Class theClass) { 1888 if (theClass == null) { 1889 throw new NullPointerException("theClass must not be null"); 1890 } 1891 myReturnBundleType = theClass; 1892 return this; 1893 } 1894 1895 @Override 1896 public IQuery revInclude(Include theInclude) { 1897 myRevInclude.add(theInclude); 1898 return this; 1899 } 1900 1901 private void setType(Class<? extends IBaseResource> theResourceType) { 1902 myResourceType = theResourceType; 1903 RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType); 1904 myResourceName = definition.getName(); 1905 } 1906 1907 private void setType(String theResourceName) { 1908 myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass(); 1909 myResourceName = theResourceName; 1910 } 1911 1912 @Override 1913 public ISort sort() { 1914 SortInternal retVal = new SortInternal(this); 1915 mySort.add(retVal); 1916 return retVal; 1917 } 1918 1919 @Override 1920 public IQuery sort(SortSpec theSortSpec) { 1921 SortSpec sortSpec = theSortSpec; 1922 while (sortSpec != null) { 1923 mySort.add(new SortInternal(sortSpec)); 1924 sortSpec = sortSpec.getChain(); 1925 } 1926 return this; 1927 } 1928 1929 @Override 1930 public IQuery usingStyle(SearchStyleEnum theStyle) { 1931 mySearchStyle = theStyle; 1932 return this; 1933 } 1934 1935 @Override 1936 public IQuery withAnyProfile(Collection theProfileUris) { 1937 Validate.notEmpty(theProfileUris, "theProfileUris must not be null or empty"); 1938 myProfiles.add(theProfileUris); 1939 return this; 1940 } 1941 1942 @Override 1943 public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) { 1944 myResourceId = theResourceId; 1945 myCompartmentName = theCompartmentName; 1946 return this; 1947 } 1948 1949 @Override 1950 public IQuery withProfile(String theProfileUri) { 1951 Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty"); 1952 myProfiles.add(Collections.singletonList(theProfileUri)); 1953 return this; 1954 } 1955 1956 @Override 1957 public IQuery withSecurity(String theSystem, String theCode) { 1958 Validate.notBlank(theCode, "theCode must not be null or empty"); 1959 mySecurity.add(new TokenParam(theSystem, theCode)); 1960 return this; 1961 } 1962 1963 @Override 1964 public IQuery withTag(String theSystem, String theCode) { 1965 Validate.notBlank(theCode, "theCode must not be null or empty"); 1966 myTags.add(new TokenParam(theSystem, theCode)); 1967 return this; 1968 } 1969 1970 } 1971 1972 private final class StringResponseHandler implements IClientResponseHandler<String> { 1973 1974 @Override 1975 public String invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) 1976 throws IOException, BaseServerResponseException { 1977 return IOUtils.toString(theResponseInputStream, Charsets.UTF_8); 1978 } 1979 } 1980 1981 private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T> { 1982 1983 private IBaseBundle myBaseBundle; 1984 private String myRawBundle; 1985 private EncodingEnum myRawBundleEncoding; 1986 private List<? extends IBaseResource> myResources; 1987 1988 public TransactionExecutable(IBaseBundle theBundle) { 1989 myBaseBundle = theBundle; 1990 } 1991 1992 public TransactionExecutable(List<? extends IBaseResource> theResources) { 1993 myResources = theResources; 1994 } 1995 1996 public TransactionExecutable(String theBundle) { 1997 myRawBundle = theBundle; 1998 myRawBundleEncoding = EncodingEnum.detectEncodingNoDefault(myRawBundle); 1999 if (myRawBundleEncoding == null) { 2000 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 2001 } 2002 } 2003 2004 @SuppressWarnings({"unchecked", "rawtypes"}) 2005 @Override 2006 public T execute() { 2007 Map<String, List<String>> params = new HashMap<String, List<String>>(); 2008 if (myResources != null) { 2009 ResourceListResponseHandler binding = new ResourceListResponseHandler(); 2010 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext); 2011 return (T) invoke(params, binding, invocation); 2012 } else if (myBaseBundle != null) { 2013 ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes()); 2014 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext); 2015 return (T) invoke(params, binding, invocation); 2016 // } else if (myRawBundle != null) { 2017 } else { 2018 StringResponseHandler binding = new StringResponseHandler(); 2019 /* 2020 * If the user has explicitly requested a given encoding, we may need to re-encode the raw string 2021 */ 2022 if (getParamEncoding() != null) { 2023 if (EncodingEnum.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) { 2024 IBaseResource parsed = parseResourceBody(myRawBundle); 2025 myRawBundle = getParamEncoding().newParser(getFhirContext()).encodeResourceToString(parsed); 2026 } 2027 } 2028 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myRawBundle, myContext); 2029 return (T) invoke(params, binding, invocation); 2030 } 2031 } 2032 2033 } 2034 2035 private final class TransactionInternal implements ITransaction { 2036 2037 @Override 2038 public ITransactionTyped<String> withBundle(String theBundle) { 2039 Validate.notBlank(theBundle, "theBundle must not be null"); 2040 return new TransactionExecutable<String>(theBundle); 2041 } 2042 2043 @Override 2044 public <T extends IBaseBundle> ITransactionTyped<T> withBundle(T theBundle) { 2045 Validate.notNull(theBundle, "theBundle must not be null"); 2046 return new TransactionExecutable<T>(theBundle); 2047 } 2048 2049 @Override 2050 public ITransactionTyped<List<IBaseResource>> withResources(List<? extends IBaseResource> theResources) { 2051 Validate.notNull(theResources, "theResources must not be null"); 2052 2053 for (IBaseResource next : theResources) { 2054 String entryMethod = null; 2055 if (next instanceof IResource) { 2056 BundleEntryTransactionMethodEnum entryMethodEnum = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) next); 2057 if (entryMethodEnum != null) { 2058 entryMethod = entryMethodEnum.getCode(); 2059 } 2060 } else { 2061 entryMethod = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IAnyResource) next); 2062 } 2063 2064 if (isBlank(entryMethod)) { 2065 if (isBlank(next.getIdElement().getValue())) { 2066 entryMethod = "POST"; 2067 } else { 2068 entryMethod = "PUT"; 2069 } 2070 if (next instanceof IResource) { 2071 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put((IResource) next, BundleEntryTransactionMethodEnum.valueOf(entryMethod)); 2072 } else { 2073 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put((IAnyResource) next, entryMethod); 2074 } 2075 2076 } 2077 2078 } 2079 2080 return new TransactionExecutable<>(theResources); 2081 } 2082 2083 } 2084 2085 private class UpdateInternal extends BaseSearch<IUpdateExecutable, IUpdateWithQueryTyped, MethodOutcome> 2086 implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { 2087 2088 private boolean myConditional; 2089 private IIdType myId; 2090 private PreferReturnEnum myPrefer; 2091 private IBaseResource myResource; 2092 private String myResourceBody; 2093 private String mySearchUrl; 2094 2095 @Override 2096 public IUpdateWithQuery conditional() { 2097 myConditional = true; 2098 return this; 2099 } 2100 2101 @Override 2102 public IUpdateTyped conditionalByUrl(String theSearchUrl) { 2103 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 2104 return this; 2105 } 2106 2107 @Override 2108 public MethodOutcome execute() { 2109 if (myResource == null) { 2110 myResource = parseResourceBody(myResourceBody); 2111 } 2112 2113 // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding 2114 if (getParamEncoding() != null) { 2115 myResourceBody = null; 2116 } 2117 2118 BaseHttpClientInvocation invocation; 2119 if (mySearchUrl != null) { 2120 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl); 2121 } else if (myConditional) { 2122 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, getParamMap()); 2123 } else { 2124 if (myId == null) { 2125 myId = myResource.getIdElement(); 2126 } 2127 2128 if (myId == null || myId.hasIdPart() == false) { 2129 throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); 2130 } 2131 invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); 2132 } 2133 2134 addPreferHeader(myPrefer, invocation); 2135 2136 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 2137 2138 Map<String, List<String>> params = new HashMap<String, List<String>>(); 2139 return invoke(params, binding, invocation); 2140 2141 } 2142 2143 @Override 2144 public IUpdateExecutable prefer(PreferReturnEnum theReturn) { 2145 myPrefer = theReturn; 2146 return this; 2147 } 2148 2149 @Override 2150 public IUpdateTyped resource(IBaseResource theResource) { 2151 Validate.notNull(theResource, "Resource can not be null"); 2152 myResource = theResource; 2153 return this; 2154 } 2155 2156 @Override 2157 public IUpdateTyped resource(String theResourceBody) { 2158 Validate.notBlank(theResourceBody, "Body can not be null or blank"); 2159 myResourceBody = theResourceBody; 2160 return this; 2161 } 2162 2163 @Override 2164 public IUpdateExecutable withId(IIdType theId) { 2165 if (theId == null) { 2166 throw new NullPointerException("theId can not be null"); 2167 } 2168 if (theId.hasIdPart() == false) { 2169 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue()); 2170 } 2171 myId = theId; 2172 return this; 2173 } 2174 2175 @Override 2176 public IUpdateExecutable withId(String theId) { 2177 if (theId == null) { 2178 throw new NullPointerException("theId can not be null"); 2179 } 2180 if (isBlank(theId)) { 2181 throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId); 2182 } 2183 myId = new IdDt(theId); 2184 return this; 2185 } 2186 2187 } 2188 2189 private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome> implements IValidate, IValidateUntyped { 2190 private IBaseResource myResource; 2191 2192 @Override 2193 public MethodOutcome execute() { 2194 BaseHttpClientInvocation invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, myResource); 2195 ResourceResponseHandler<BaseOperationOutcome> handler = new ResourceResponseHandler<BaseOperationOutcome>(null, null); 2196 IBaseOperationOutcome outcome = invoke(null, handler, invocation); 2197 MethodOutcome retVal = new MethodOutcome(); 2198 retVal.setOperationOutcome(outcome); 2199 return retVal; 2200 } 2201 2202 @Override 2203 public IValidateUntyped resource(IBaseResource theResource) { 2204 Validate.notNull(theResource, "theResource must not be null"); 2205 myResource = theResource; 2206 return this; 2207 } 2208 2209 @Override 2210 public IValidateUntyped resource(String theResourceRaw) { 2211 Validate.notBlank(theResourceRaw, "theResourceRaw must not be null or blank"); 2212 myResource = parseResourceBody(theResourceRaw); 2213 2214 EncodingEnum enc = EncodingEnum.detectEncodingNoDefault(theResourceRaw); 2215 if (enc == null) { 2216 throw new IllegalArgumentException(myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 2217 } 2218 switch (enc) { 2219 case XML: 2220 encodedXml(); 2221 break; 2222 case JSON: 2223 encodedJson(); 2224 break; 2225 } 2226 return this; 2227 } 2228 2229 } 2230 2231 @SuppressWarnings("rawtypes") 2232 private static class SortInternal implements ISort { 2233 2234 private SortOrderEnum myDirection; 2235 private SearchInternal myFor; 2236 private String myParamName; 2237 private String myParamValue; 2238 2239 public SortInternal(SearchInternal theFor) { 2240 myFor = theFor; 2241 } 2242 2243 public SortInternal(SortSpec theSortSpec) { 2244 if (theSortSpec.getOrder() == null) { 2245 myParamName = Constants.PARAM_SORT; 2246 } else if (theSortSpec.getOrder() == SortOrderEnum.ASC) { 2247 myParamName = Constants.PARAM_SORT_ASC; 2248 } else if (theSortSpec.getOrder() == SortOrderEnum.DESC) { 2249 myParamName = Constants.PARAM_SORT_DESC; 2250 } 2251 myDirection = theSortSpec.getOrder(); 2252 myParamValue = theSortSpec.getParamName(); 2253 } 2254 2255 @Override 2256 public IQuery ascending(IParam theParam) { 2257 myParamName = Constants.PARAM_SORT_ASC; 2258 myDirection = SortOrderEnum.ASC; 2259 myParamValue = theParam.getParamName(); 2260 return myFor; 2261 } 2262 2263 @Override 2264 public IQuery ascending(String theParam) { 2265 myParamName = Constants.PARAM_SORT_ASC; 2266 myDirection = SortOrderEnum.ASC; 2267 myParamValue = theParam; 2268 return myFor; 2269 } 2270 2271 @Override 2272 public IQuery defaultOrder(IParam theParam) { 2273 myParamName = Constants.PARAM_SORT; 2274 myDirection = null; 2275 myParamValue = theParam.getParamName(); 2276 return myFor; 2277 } 2278 2279 @Override 2280 public IQuery defaultOrder(String theParam) { 2281 myParamName = Constants.PARAM_SORT; 2282 myDirection = null; 2283 myParamValue = theParam; 2284 return myFor; 2285 } 2286 2287 @Override 2288 public IQuery descending(IParam theParam) { 2289 myParamName = Constants.PARAM_SORT_DESC; 2290 myDirection = SortOrderEnum.DESC; 2291 myParamValue = theParam.getParamName(); 2292 return myFor; 2293 } 2294 2295 @Override 2296 public IQuery descending(String theParam) { 2297 myParamName = Constants.PARAM_SORT_DESC; 2298 myDirection = SortOrderEnum.DESC; 2299 myParamValue = theParam; 2300 return myFor; 2301 } 2302 2303 public SortOrderEnum getDirection() { 2304 return myDirection; 2305 } 2306 2307 public String getParamName() { 2308 return myParamName; 2309 } 2310 2311 public String getParamValue() { 2312 return myParamValue; 2313 } 2314 2315 } 2316 2317 private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) { 2318 if (!params.containsKey(parameterName)) { 2319 params.put(parameterName, new ArrayList<String>()); 2320 } 2321 params.get(parameterName).add(parameterValue); 2322 } 2323 2324 private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) { 2325 if (thePrefer != null) { 2326 theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue()); 2327 } 2328 } 2329 2330 private static String validateAndEscapeConditionalUrl(String theSearchUrl) { 2331 Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null"); 2332 StringBuilder b = new StringBuilder(); 2333 boolean haveHadQuestionMark = false; 2334 for (int i = 0; i < theSearchUrl.length(); i++) { 2335 char nextChar = theSearchUrl.charAt(i); 2336 if (!haveHadQuestionMark) { 2337 if (nextChar == '?') { 2338 haveHadQuestionMark = true; 2339 } else if (!Character.isLetter(nextChar)) { 2340 throw new IllegalArgumentException("Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl); 2341 } 2342 b.append(nextChar); 2343 } else { 2344 switch (nextChar) { 2345 case '|': 2346 case '?': 2347 case '$': 2348 case ':': 2349 b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar))); 2350 break; 2351 default: 2352 b.append(nextChar); 2353 break; 2354 } 2355 } 2356 } 2357 return b.toString(); 2358 } 2359 2360}