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