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