
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 returnStringOutcome() { 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 private boolean myIsHistoryRewrite; 1745 1746 @Override 1747 public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) { 1748 Validate.notNull(theClass, "theClass must not be null"); 1749 String resourceType = myContext.getResourceType(theClass); 1750 return conditional(resourceType); 1751 } 1752 1753 @Override 1754 public IPatchWithQuery conditional(String theResourceType) { 1755 Validate.notBlank(theResourceType, "theResourceType must not be null"); 1756 myResourceType = theResourceType; 1757 myConditional = true; 1758 return this; 1759 } 1760 1761 // TODO: This is not longer used.. Deprecate it or just remove it? 1762 @Override 1763 public IPatchWithBody conditionalByUrl(String theSearchUrl) { 1764 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 1765 return this; 1766 } 1767 1768 @Override 1769 public MethodOutcome execute() { 1770 1771 if (myPatchType == null) { 1772 throw new InvalidRequestException(Msg.code(1383) + "No patch type supplied, cannot invoke server"); 1773 } 1774 if (myPatchBody == null) { 1775 throw new InvalidRequestException(Msg.code(1384) + "No patch body supplied, cannot invoke server"); 1776 } 1777 1778 BaseHttpClientInvocation invocation; 1779 if (myIsHistoryRewrite) { 1780 if (!myId.hasVersionIdPart()) { 1781 throw new InvalidRequestException(Msg.code(2716) 1782 + "Invalid resource ID for rewrite history: ID must contain a history version"); 1783 } 1784 // createPatchInvocation() eventually calls HttpPatchClientInvocation 1785 // passing in the ID as a URL (String) will keep the version whereas passing in the raw ID will remove 1786 // the version 1787 invocation = MethodUtil.createPatchInvocation(myContext, myId.getValue(), myPatchType, myPatchBody); 1788 invocation.addHeader(Constants.HEADER_REWRITE_HISTORY, "true"); 1789 } else if (isNotBlank(mySearchUrl)) { 1790 invocation = MethodUtil.createPatchInvocation(myContext, mySearchUrl, myPatchType, myPatchBody); 1791 } else if (myConditional) { 1792 invocation = MethodUtil.createPatchInvocation( 1793 myContext, myPatchType, myPatchBody, myResourceType, getParamMap()); 1794 } else { 1795 if (myId == null || !myId.hasIdPart()) { 1796 throw new InvalidRequestException( 1797 Msg.code(1385) + "No ID supplied for resource to patch, can not invoke server"); 1798 } 1799 invocation = MethodUtil.createPatchInvocation(myContext, myId, myPatchType, myPatchBody); 1800 } 1801 1802 addPreferHeader(myPrefer, invocation); 1803 1804 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 1805 1806 Map<String, List<String>> params = new HashMap<>(); 1807 return invoke(params, binding, invocation); 1808 } 1809 1810 @Override 1811 public IPatchExecutable prefer(PreferReturnEnum theReturn) { 1812 myPrefer = theReturn; 1813 return this; 1814 } 1815 1816 @Override 1817 public IPatchWithBody withBody(String thePatchBody) { 1818 Validate.notBlank(thePatchBody, "thePatchBody must not be blank"); 1819 1820 myPatchBody = thePatchBody; 1821 1822 EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(thePatchBody); 1823 if (encoding == EncodingEnum.XML) { 1824 myPatchType = PatchTypeEnum.XML_PATCH; 1825 } else if (encoding == EncodingEnum.JSON) { 1826 myPatchType = PatchTypeEnum.JSON_PATCH; 1827 } else { 1828 throw new IllegalArgumentException(Msg.code(1386) + "Unable to determine encoding of patch"); 1829 } 1830 1831 return this; 1832 } 1833 1834 @Override 1835 public IPatchWithBody withFhirPatch(IBaseParameters thePatchBody) { 1836 Validate.notNull(thePatchBody, "thePatchBody must not be null"); 1837 1838 myPatchType = PatchTypeEnum.FHIR_PATCH_JSON; 1839 myPatchBody = myContext.newJsonParser().encodeResourceToString(thePatchBody); 1840 1841 return this; 1842 } 1843 1844 @Override 1845 public IPatchExecutable withId(IIdType theId) { 1846 if (theId == null) { 1847 throw new NullPointerException(Msg.code(1387) + "theId can not be null"); 1848 } 1849 Validate.notBlank( 1850 theId.getIdPart(), 1851 "theId must not be blank and must contain a resource type and ID (e.g. \"Patient/123\"), found: %s", 1852 UrlUtil.sanitizeUrlPart(theId.getValue())); 1853 Validate.notBlank( 1854 theId.getResourceType(), 1855 "theId must not be blank and must contain a resource type and ID (e.g. \"Patient/123\"), found: %s", 1856 UrlUtil.sanitizeUrlPart(theId.getValue())); 1857 myId = theId; 1858 return this; 1859 } 1860 1861 @Override 1862 public IPatchExecutable withId(String theId) { 1863 if (theId == null) { 1864 throw new NullPointerException(Msg.code(1388) + "theId can not be null"); 1865 } 1866 return withId(new IdDt(theId)); 1867 } 1868 1869 @Override 1870 public IPatchWithBody historyRewrite() { 1871 myIsHistoryRewrite = true; 1872 return this; 1873 } 1874 } 1875 1876 @SuppressWarnings({"rawtypes", "unchecked"}) 1877 private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable { 1878 private IIdType myId; 1879 private String myIfVersionMatches; 1880 private ICallable myNotModifiedHandler; 1881 private RuntimeResourceDefinition myType; 1882 1883 @Override 1884 public Object execute() { // AAA 1885 if (myId.hasVersionIdPart()) { 1886 return doReadOrVRead( 1887 myType.getImplementingClass(), 1888 myId, 1889 true, 1890 myNotModifiedHandler, 1891 myIfVersionMatches, 1892 myPrettyPrint, 1893 mySummaryMode, 1894 myParamEncoding, 1895 getSubsetElements(), 1896 getCustomAcceptHeaderValue(), 1897 myCustomHeaderValues); 1898 } 1899 return doReadOrVRead( 1900 myType.getImplementingClass(), 1901 myId, 1902 false, 1903 myNotModifiedHandler, 1904 myIfVersionMatches, 1905 myPrettyPrint, 1906 mySummaryMode, 1907 myParamEncoding, 1908 getSubsetElements(), 1909 getCustomAcceptHeaderValue(), 1910 myCustomHeaderValues); 1911 } 1912 1913 @Override 1914 public IReadIfNoneMatch ifVersionMatches(String theVersion) { 1915 myIfVersionMatches = theVersion; 1916 return new IReadIfNoneMatch() { 1917 1918 @Override 1919 public IReadExecutable returnNull() { 1920 myNotModifiedHandler = () -> null; 1921 return ReadInternal.this; 1922 } 1923 1924 @Override 1925 public IReadExecutable returnResource(final IBaseResource theInstance) { 1926 myNotModifiedHandler = () -> theInstance; 1927 return ReadInternal.this; 1928 } 1929 1930 @Override 1931 public IReadExecutable throwNotModifiedException() { 1932 myNotModifiedHandler = null; 1933 return ReadInternal.this; 1934 } 1935 }; 1936 } 1937 1938 private void processUrl() { 1939 String resourceType = myId.getResourceType(); 1940 if (isBlank(resourceType)) { 1941 throw new IllegalArgumentException( 1942 Msg.code(1389) + myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId)); 1943 } 1944 myType = myContext.getResourceDefinition(resourceType); 1945 if (myType == null) { 1946 throw new IllegalArgumentException( 1947 Msg.code(1390) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId)); 1948 } 1949 } 1950 1951 @Override 1952 public <T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType) { 1953 Validate.notNull(theResourceType, "theResourceType must not be null"); 1954 myType = myContext.getResourceDefinition(theResourceType); 1955 if (myType == null) { 1956 throw new IllegalArgumentException(Msg.code(1391) 1957 + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); 1958 } 1959 return this; 1960 } 1961 1962 @Override 1963 public IReadTyped<IBaseResource> resource(String theResourceAsText) { 1964 Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText"); 1965 myType = myContext.getResourceDefinition(theResourceAsText); 1966 if (myType == null) { 1967 throw new IllegalArgumentException(Msg.code(1392) 1968 + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText)); 1969 } 1970 return this; 1971 } 1972 1973 @Override 1974 public IReadExecutable withId(IIdType theId) { 1975 Validate.notNull(theId, "The ID can not be null"); 1976 Validate.notBlank(theId.getIdPart(), "The ID can not be blank"); 1977 myId = theId.toUnqualified(); 1978 return this; 1979 } 1980 1981 @Override 1982 public IReadExecutable withId(Long theId) { 1983 Validate.notNull(theId, "The ID can not be null"); 1984 myId = new IdDt(myType.getName(), theId); 1985 return this; 1986 } 1987 1988 @Override 1989 public IReadExecutable withId(String theId) { 1990 Validate.notBlank(theId, "The ID can not be blank"); 1991 if (theId.indexOf('/') == -1) { 1992 myId = new IdDt(myType.getName(), theId); 1993 } else { 1994 myId = new IdDt(theId); 1995 } 1996 return this; 1997 } 1998 1999 @Override 2000 public IReadExecutable withIdAndVersion(String theId, String theVersion) { 2001 Validate.notBlank(theId, "The ID can not be blank"); 2002 myId = new IdDt(myType.getName(), theId, theVersion); 2003 return this; 2004 } 2005 2006 @Override 2007 public IReadExecutable withUrl(IIdType theUrl) { 2008 Validate.notNull(theUrl, "theUrl can not be null"); 2009 myId = theUrl; 2010 processUrl(); 2011 return this; 2012 } 2013 2014 @Override 2015 public IReadExecutable withUrl(String theUrl) { 2016 myId = new IdDt(theUrl); 2017 processUrl(); 2018 return this; 2019 } 2020 } 2021 2022 private final class ResourceListResponseHandler implements IClientResponseHandler<List<IBaseResource>> { 2023 2024 @SuppressWarnings("unchecked") 2025 @Override 2026 public List<IBaseResource> invokeClient( 2027 String theResponseMimeType, 2028 InputStream theResponseInputStream, 2029 int theResponseStatusCode, 2030 Map<String, List<String>> theHeaders) 2031 throws BaseServerResponseException { 2032 Class<? extends IBaseResource> bundleType = 2033 myContext.getResourceDefinition("Bundle").getImplementingClass(); 2034 ResourceResponseHandler<IBaseResource> handler = 2035 new ResourceResponseHandler<>((Class<IBaseResource>) bundleType); 2036 IBaseResource response = handler.invokeClient( 2037 theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders); 2038 IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); 2039 bundleFactory.initializeWithBundleResource(response); 2040 return bundleFactory.toListOfResources(); 2041 } 2042 } 2043 2044 @SuppressWarnings({"rawtypes", "unchecked"}) 2045 private class SearchInternal<OUTPUT> extends BaseSearch<IQuery<OUTPUT>, IQuery<OUTPUT>, OUTPUT> 2046 implements IQuery<OUTPUT>, IUntypedQuery<IQuery<OUTPUT>> { 2047 2048 private String myCompartmentName; 2049 private final List<Include> myInclude = new ArrayList<>(); 2050 private DateRangeParam myLastUpdated; 2051 private Integer myParamLimit; 2052 private Integer myParamOffset; 2053 private final List<Collection<String>> myProfiles = new ArrayList<>(); 2054 private String myResourceId; 2055 private String myResourceName; 2056 private Class<? extends IBaseResource> myResourceType; 2057 private Class<? extends IBaseBundle> myReturnBundleType; 2058 private final List<Include> myRevInclude = new ArrayList<>(); 2059 private SearchStyleEnum mySearchStyle; 2060 private String mySearchUrl; 2061 private final List<TokenParam> mySecurity = new ArrayList<>(); 2062 private final List<SortInternal> mySort = new ArrayList<>(); 2063 private final List<TokenParam> myTags = new ArrayList<>(); 2064 private SearchTotalModeEnum myTotalMode; 2065 2066 public SearchInternal() { 2067 myResourceType = null; 2068 myResourceName = null; 2069 mySearchUrl = null; 2070 } 2071 2072 @Override 2073 public IQuery byUrl(String theSearchUrl) { 2074 Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null"); 2075 2076 if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) { 2077 mySearchUrl = theSearchUrl; 2078 int qIndex = mySearchUrl.indexOf('?'); 2079 if (qIndex != -1) { 2080 mySearchUrl = mySearchUrl.substring(0, qIndex) 2081 + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex)); 2082 } 2083 } else { 2084 String searchUrl = theSearchUrl; 2085 if (searchUrl.startsWith("/")) { 2086 searchUrl = searchUrl.substring(1); 2087 } 2088 if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) { 2089 throw new IllegalArgumentException( 2090 Msg.code(1393) 2091 + "Search URL must be either a complete URL starting with http: or https:, or a relative FHIR URL in the form [ResourceType]?[Params]"); 2092 } 2093 int qIndex = searchUrl.indexOf('?'); 2094 if (qIndex == -1) { 2095 mySearchUrl = getUrlBase() + '/' + searchUrl; 2096 } else { 2097 mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl); 2098 } 2099 } 2100 return this; 2101 } 2102 2103 @Override 2104 public IQuery count(int theLimitTo) { 2105 if (theLimitTo > 0) { 2106 myParamLimit = theLimitTo; 2107 } else { 2108 myParamLimit = null; 2109 } 2110 return this; 2111 } 2112 2113 @Override 2114 public IQuery offset(int theOffset) { 2115 if (theOffset >= 0) { 2116 myParamOffset = theOffset; 2117 } else { 2118 myParamOffset = null; 2119 } 2120 return this; 2121 } 2122 2123 @Override 2124 public OUTPUT execute() { 2125 2126 Map<String, List<String>> params = getParamMap(); 2127 2128 for (TokenParam next : myTags) { 2129 addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken(myContext)); 2130 } 2131 2132 for (TokenParam next : mySecurity) { 2133 addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken(myContext)); 2134 } 2135 2136 for (Collection<String> profileUris : myProfiles) { 2137 StringBuilder builder = new StringBuilder(); 2138 for (Iterator<String> profileItr = profileUris.iterator(); profileItr.hasNext(); ) { 2139 builder.append(profileItr.next()); 2140 if (profileItr.hasNext()) { 2141 builder.append(','); 2142 } 2143 } 2144 addParam(params, Constants.PARAM_PROFILE, builder.toString()); 2145 } 2146 2147 for (Include next : myInclude) { 2148 if (next.isRecurse()) { 2149 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 2150 addParam(params, Constants.PARAM_INCLUDE_ITERATE, next.getValue()); 2151 } else { 2152 addParam(params, Constants.PARAM_INCLUDE_RECURSE, next.getValue()); 2153 } 2154 } else { 2155 addParam(params, Constants.PARAM_INCLUDE, next.getValue()); 2156 } 2157 } 2158 2159 for (Include next : myRevInclude) { 2160 if (next.isRecurse()) { 2161 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 2162 addParam(params, Constants.PARAM_REVINCLUDE_ITERATE, next.getValue()); 2163 } else { 2164 addParam(params, Constants.PARAM_REVINCLUDE_RECURSE, next.getValue()); 2165 } 2166 } else { 2167 addParam(params, Constants.PARAM_REVINCLUDE, next.getValue()); 2168 } 2169 } 2170 2171 if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) { 2172 SortSpec rootSs = null; 2173 SortSpec lastSs = null; 2174 for (SortInternal next : mySort) { 2175 SortSpec nextSortSpec = new SortSpec(); 2176 nextSortSpec.setParamName(next.getParamValue()); 2177 nextSortSpec.setOrder(next.getDirection()); 2178 if (rootSs == null) { 2179 rootSs = nextSortSpec; 2180 } else { 2181 lastSs.setChain(nextSortSpec); 2182 } 2183 lastSs = nextSortSpec; 2184 } 2185 if (rootSs != null) { 2186 addParam(params, Constants.PARAM_SORT, SortParameter.createSortStringDstu3(rootSs)); 2187 } 2188 } else { 2189 for (SortInternal next : mySort) { 2190 addParam(params, next.getParamName(), next.getParamValue()); 2191 } 2192 } 2193 2194 if (myParamLimit != null) { 2195 addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit)); 2196 } 2197 2198 if (myParamOffset != null) { 2199 addParam(params, Constants.PARAM_OFFSET, Integer.toString(myParamOffset)); 2200 } 2201 2202 if (myLastUpdated != null) { 2203 for (DateParam next : myLastUpdated.getValuesAsQueryTokens()) { 2204 addParam(params, Constants.PARAM_LASTUPDATED, next.getValueAsQueryToken(myContext)); 2205 } 2206 } 2207 2208 if (myTotalMode != null) { 2209 addParam(params, Constants.PARAM_SEARCH_TOTAL_MODE, myTotalMode.getCode()); 2210 } 2211 2212 IClientResponseHandler<? extends IBase> binding; 2213 binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType)); 2214 2215 IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null; 2216 2217 BaseHttpClientInvocation invocation; 2218 if (mySearchUrl != null) { 2219 invocation = SearchMethodBinding.createSearchInvocation( 2220 myContext, mySearchUrl, UrlSourceEnum.EXPLICIT, params); 2221 } else { 2222 invocation = SearchMethodBinding.createSearchInvocation( 2223 myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle); 2224 } 2225 2226 return (OUTPUT) invoke(params, binding, invocation); 2227 } 2228 2229 @Override 2230 public IQuery forAllResources() { 2231 return this; 2232 } 2233 2234 @Override 2235 public IQuery forResource(Class theResourceType) { 2236 setType(theResourceType); 2237 return this; 2238 } 2239 2240 @Override 2241 public IQuery forResource(String theResourceName) { 2242 setType(theResourceName); 2243 return this; 2244 } 2245 2246 @Override 2247 public IQuery include(Include theInclude) { 2248 myInclude.add(theInclude); 2249 return this; 2250 } 2251 2252 @Override 2253 public IQuery lastUpdated(DateRangeParam theLastUpdated) { 2254 myLastUpdated = theLastUpdated; 2255 return this; 2256 } 2257 2258 @Deprecated // override deprecated method 2259 @Override 2260 public IQuery limitTo(int theLimitTo) { 2261 return count(theLimitTo); 2262 } 2263 2264 @Override 2265 public IQuery<OUTPUT> totalMode(SearchTotalModeEnum theSearchTotalModeEnum) { 2266 myTotalMode = theSearchTotalModeEnum; 2267 return this; 2268 } 2269 2270 @Override 2271 public IQuery returnBundle(Class theClass) { 2272 if (theClass == null) { 2273 throw new NullPointerException(Msg.code(1394) + "theClass must not be null"); 2274 } 2275 myReturnBundleType = theClass; 2276 return this; 2277 } 2278 2279 @Override 2280 public IQuery revInclude(Include theInclude) { 2281 myRevInclude.add(theInclude); 2282 return this; 2283 } 2284 2285 private void setType(Class<? extends IBaseResource> theResourceType) { 2286 myResourceType = theResourceType; 2287 RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType); 2288 myResourceName = definition.getName(); 2289 } 2290 2291 private void setType(String theResourceName) { 2292 myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass(); 2293 myResourceName = theResourceName; 2294 } 2295 2296 @Override 2297 public ISort sort() { 2298 SortInternal retVal = new SortInternal(this); 2299 mySort.add(retVal); 2300 return retVal; 2301 } 2302 2303 @Override 2304 public IQuery sort(SortSpec theSortSpec) { 2305 SortSpec sortSpec = theSortSpec; 2306 while (sortSpec != null) { 2307 mySort.add(new SortInternal(sortSpec)); 2308 sortSpec = sortSpec.getChain(); 2309 } 2310 return this; 2311 } 2312 2313 @Override 2314 public IQuery usingStyle(SearchStyleEnum theStyle) { 2315 mySearchStyle = theStyle; 2316 return this; 2317 } 2318 2319 @Override 2320 public IQuery withAnyProfile(Collection theProfileUris) { 2321 Validate.notEmpty(theProfileUris, "theProfileUris must not be null or empty"); 2322 myProfiles.add(theProfileUris); 2323 return this; 2324 } 2325 2326 @Override 2327 public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) { 2328 myResourceId = theResourceId; 2329 myCompartmentName = theCompartmentName; 2330 return this; 2331 } 2332 2333 @Override 2334 public IQuery withProfile(String theProfileUri) { 2335 Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty"); 2336 myProfiles.add(Collections.singletonList(theProfileUri)); 2337 return this; 2338 } 2339 2340 @Override 2341 public IQuery withSecurity(String theSystem, String theCode) { 2342 Validate.notBlank(theCode, "theCode must not be null or empty"); 2343 mySecurity.add(new TokenParam(theSystem, theCode)); 2344 return this; 2345 } 2346 2347 @Override 2348 public IQuery withTag(String theSystem, String theCode) { 2349 Validate.notBlank(theCode, "theCode must not be null or empty"); 2350 myTags.add(new TokenParam(theSystem, theCode)); 2351 return this; 2352 } 2353 } 2354 2355 private static final class StringResponseHandler implements IClientResponseHandler<String> { 2356 2357 @Override 2358 public String invokeClient( 2359 String theResponseMimeType, 2360 InputStream theResponseInputStream, 2361 int theResponseStatusCode, 2362 Map<String, List<String>> theHeaders) 2363 throws IOException, BaseServerResponseException { 2364 return IOUtils.toString(theResponseInputStream, Charsets.UTF_8); 2365 } 2366 } 2367 2368 private static final class StringOutcomeResponseHandler implements IClientResponseHandler<StringOutcome> { 2369 2370 @Override 2371 public StringOutcome invokeClient( 2372 String theResponseMimeType, 2373 InputStream theResponseInputStream, 2374 int theResponseStatusCode, 2375 Map<String, List<String>> theHeaders) 2376 throws IOException, BaseServerResponseException { 2377 2378 String payload = IOUtils.toString(theResponseInputStream, Charsets.UTF_8); 2379 return new StringOutcome(theResponseStatusCode, payload, theHeaders); 2380 } 2381 } 2382 2383 private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> 2384 implements ITransactionTyped<T> { 2385 2386 private IBaseBundle myBaseBundle; 2387 private String myRawBundle; 2388 private EncodingEnum myRawBundleEncoding; 2389 private List<? extends IBaseResource> myResources; 2390 2391 public TransactionExecutable(IBaseBundle theBundle) { 2392 myBaseBundle = theBundle; 2393 } 2394 2395 public TransactionExecutable(List<? extends IBaseResource> theResources) { 2396 myResources = theResources; 2397 } 2398 2399 public TransactionExecutable(String theBundle) { 2400 myRawBundle = theBundle; 2401 myRawBundleEncoding = EncodingEnum.detectEncodingNoDefault(myRawBundle); 2402 if (myRawBundleEncoding == null) { 2403 throw new IllegalArgumentException(Msg.code(1395) 2404 + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 2405 } 2406 } 2407 2408 @SuppressWarnings({"unchecked", "rawtypes"}) 2409 @Override 2410 public T execute() { 2411 Map<String, List<String>> params = new HashMap<>(); 2412 if (myResources != null) { 2413 ResourceListResponseHandler binding = new ResourceListResponseHandler(); 2414 BaseHttpClientInvocation invocation = 2415 TransactionMethodBinding.createTransactionInvocation(myResources, myContext); 2416 return (T) invoke(params, binding, invocation); 2417 } else if (myBaseBundle != null) { 2418 ResourceResponseHandler binding = 2419 new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes()); 2420 BaseHttpClientInvocation invocation = 2421 TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext); 2422 return (T) invoke(params, binding, invocation); 2423 // } else if (myRawBundle != null) { 2424 } else { 2425 StringResponseHandler binding = new StringResponseHandler(); 2426 /* 2427 * If the user has explicitly requested a given encoding, we may need to re-encode the raw string 2428 */ 2429 if (getParamEncoding() != null) { 2430 if (EncodingEnum.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) { 2431 IBaseResource parsed = parseResourceBody(myRawBundle); 2432 myRawBundle = 2433 getParamEncoding().newParser(getFhirContext()).encodeResourceToString(parsed); 2434 } 2435 } 2436 BaseHttpClientInvocation invocation = 2437 TransactionMethodBinding.createTransactionInvocation(myRawBundle, myContext); 2438 return (T) invoke(params, binding, invocation); 2439 } 2440 } 2441 } 2442 2443 private final class TransactionInternal implements ITransaction { 2444 2445 @Override 2446 public ITransactionTyped<String> withBundle(String theBundle) { 2447 Validate.notBlank(theBundle, "theBundle must not be null"); 2448 return new TransactionExecutable<>(theBundle); 2449 } 2450 2451 @Override 2452 public <T extends IBaseBundle> ITransactionTyped<T> withBundle(T theBundle) { 2453 Validate.notNull(theBundle, "theBundle must not be null"); 2454 return new TransactionExecutable<>(theBundle); 2455 } 2456 2457 @Override 2458 public ITransactionTyped<List<IBaseResource>> withResources(List<? extends IBaseResource> theResources) { 2459 Validate.notNull(theResources, "theResources must not be null"); 2460 2461 for (IBaseResource next : theResources) { 2462 BundleEntryTransactionMethodEnum entryMethod = 2463 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(next); 2464 2465 if (entryMethod == null) { 2466 if (isBlank(next.getIdElement().getValue())) { 2467 entryMethod = BundleEntryTransactionMethodEnum.POST; 2468 } else { 2469 entryMethod = BundleEntryTransactionMethodEnum.PUT; 2470 } 2471 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(next, entryMethod); 2472 } 2473 } 2474 2475 return new TransactionExecutable<>(theResources); 2476 } 2477 } 2478 2479 private class UpdateInternal extends BaseSearch<IUpdateExecutable, IUpdateWithQueryTyped, MethodOutcome> 2480 implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { 2481 2482 private boolean myConditional; 2483 private IIdType myId; 2484 private PreferReturnEnum myPrefer; 2485 private IBaseResource myResource; 2486 private String myResourceBody; 2487 private String mySearchUrl; 2488 private boolean myIsHistoryRewrite; 2489 2490 @Override 2491 public IUpdateTyped historyRewrite() { 2492 myIsHistoryRewrite = true; 2493 return this; 2494 } 2495 2496 @Override 2497 public IUpdateWithQuery conditional() { 2498 myConditional = true; 2499 return this; 2500 } 2501 2502 @Override 2503 public IUpdateTyped conditionalByUrl(String theSearchUrl) { 2504 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 2505 return this; 2506 } 2507 2508 @Override 2509 public MethodOutcome execute() { 2510 if (myResource == null) { 2511 myResource = parseResourceBody(myResourceBody); 2512 } 2513 2514 // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding 2515 if (getParamEncoding() != null) { 2516 myResourceBody = null; 2517 } 2518 2519 BaseHttpClientInvocation invocation; 2520 if (mySearchUrl != null) { 2521 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl); 2522 } else if (myConditional) { 2523 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, getParamMap()); 2524 } else { 2525 if (myId == null) { 2526 myId = myResource.getIdElement(); 2527 } 2528 2529 if (myId == null || !myId.hasIdPart()) { 2530 throw new InvalidRequestException( 2531 Msg.code(1396) + "No ID supplied for resource to update, can not invoke server"); 2532 } 2533 2534 if (myIsHistoryRewrite) { 2535 if (!myId.hasVersionIdPart()) { 2536 throw new InvalidRequestException(Msg.code(2090) + "ID must contain a history version, found: " 2537 + myId.getVersionIdPart()); 2538 } 2539 invocation = MethodUtil.createUpdateHistoryRewriteInvocation( 2540 myResource, myResourceBody, myId, myContext); 2541 invocation.addHeader(Constants.HEADER_REWRITE_HISTORY, "true"); 2542 } else { 2543 invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); 2544 } 2545 } 2546 2547 addPreferHeader(myPrefer, invocation); 2548 2549 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 2550 2551 Map<String, List<String>> params = new HashMap<>(); 2552 return invoke(params, binding, invocation); 2553 } 2554 2555 @Override 2556 public IUpdateExecutable prefer(PreferReturnEnum theReturn) { 2557 myPrefer = theReturn; 2558 return this; 2559 } 2560 2561 @Override 2562 public IUpdateTyped resource(IBaseResource theResource) { 2563 Validate.notNull(theResource, "Resource can not be null"); 2564 myResource = theResource; 2565 return this; 2566 } 2567 2568 @Override 2569 public IUpdateTyped resource(String theResourceBody) { 2570 Validate.notBlank(theResourceBody, "Body can not be null or blank"); 2571 myResourceBody = theResourceBody; 2572 return this; 2573 } 2574 2575 @Override 2576 public IUpdateExecutable withId(IIdType theId) { 2577 if (theId == null) { 2578 throw new NullPointerException(Msg.code(1397) + "theId can not be null"); 2579 } 2580 if (!theId.hasIdPart()) { 2581 throw new NullPointerException( 2582 Msg.code(1398) + "theId must not be blank and must contain an ID, found: " + theId.getValue()); 2583 } 2584 myId = theId; 2585 return this; 2586 } 2587 2588 @Override 2589 public IUpdateExecutable withId(String theId) { 2590 if (theId == null) { 2591 throw new NullPointerException(Msg.code(1399) + "theId can not be null"); 2592 } 2593 if (isBlank(theId)) { 2594 throw new NullPointerException( 2595 Msg.code(1400) + "theId must not be blank and must contain an ID, found: " + theId); 2596 } 2597 myId = new IdDt(theId); 2598 return this; 2599 } 2600 } 2601 2602 private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome> 2603 implements IValidate, IValidateUntyped { 2604 private IBaseResource myResource; 2605 2606 @Override 2607 public MethodOutcome execute() { 2608 BaseHttpClientInvocation invocation = 2609 ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, myResource); 2610 ResourceResponseHandler<BaseOperationOutcome> handler = new ResourceResponseHandler<>(null, null); 2611 MethodOutcomeResponseHandler methodHandler = new MethodOutcomeResponseHandler(handler); 2612 return invoke(null, methodHandler, invocation); 2613 } 2614 2615 @Override 2616 public IValidateUntyped resource(IBaseResource theResource) { 2617 Validate.notNull(theResource, "theResource must not be null"); 2618 myResource = theResource; 2619 return this; 2620 } 2621 2622 @Override 2623 public IValidateUntyped resource(String theResourceRaw) { 2624 Validate.notBlank(theResourceRaw, "theResourceRaw must not be null or blank"); 2625 myResource = parseResourceBody(theResourceRaw); 2626 2627 EncodingEnum enc = EncodingEnum.detectEncodingNoDefault(theResourceRaw); 2628 if (enc == null) { 2629 throw new IllegalArgumentException(Msg.code(1401) 2630 + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 2631 } 2632 switch (enc) { 2633 case XML: 2634 encodedXml(); 2635 break; 2636 case JSON: 2637 encodedJson(); 2638 break; 2639 } 2640 return this; 2641 } 2642 } 2643 2644 @SuppressWarnings("rawtypes") 2645 private static class SortInternal implements ISort { 2646 2647 private SortOrderEnum myDirection; 2648 private SearchInternal myFor; 2649 private String myParamName; 2650 private String myParamValue; 2651 2652 public SortInternal(SearchInternal theFor) { 2653 myFor = theFor; 2654 } 2655 2656 public SortInternal(SortSpec theSortSpec) { 2657 if (theSortSpec.getOrder() == null) { 2658 myParamName = Constants.PARAM_SORT; 2659 } else if (theSortSpec.getOrder() == SortOrderEnum.ASC) { 2660 myParamName = Constants.PARAM_SORT_ASC; 2661 } else if (theSortSpec.getOrder() == SortOrderEnum.DESC) { 2662 myParamName = Constants.PARAM_SORT_DESC; 2663 } 2664 myDirection = theSortSpec.getOrder(); 2665 myParamValue = theSortSpec.getParamName(); 2666 } 2667 2668 @Override 2669 public IQuery ascending(IParam theParam) { 2670 myParamName = Constants.PARAM_SORT_ASC; 2671 myDirection = SortOrderEnum.ASC; 2672 myParamValue = theParam.getParamName(); 2673 return myFor; 2674 } 2675 2676 @Override 2677 public IQuery ascending(String theParam) { 2678 myParamName = Constants.PARAM_SORT_ASC; 2679 myDirection = SortOrderEnum.ASC; 2680 myParamValue = theParam; 2681 return myFor; 2682 } 2683 2684 @Override 2685 public IQuery defaultOrder(IParam theParam) { 2686 myParamName = Constants.PARAM_SORT; 2687 myDirection = null; 2688 myParamValue = theParam.getParamName(); 2689 return myFor; 2690 } 2691 2692 @Override 2693 public IQuery defaultOrder(String theParam) { 2694 myParamName = Constants.PARAM_SORT; 2695 myDirection = null; 2696 myParamValue = theParam; 2697 return myFor; 2698 } 2699 2700 @Override 2701 public IQuery descending(IParam theParam) { 2702 myParamName = Constants.PARAM_SORT_DESC; 2703 myDirection = SortOrderEnum.DESC; 2704 myParamValue = theParam.getParamName(); 2705 return myFor; 2706 } 2707 2708 @Override 2709 public IQuery descending(String theParam) { 2710 myParamName = Constants.PARAM_SORT_DESC; 2711 myDirection = SortOrderEnum.DESC; 2712 myParamValue = theParam; 2713 return myFor; 2714 } 2715 2716 public SortOrderEnum getDirection() { 2717 return myDirection; 2718 } 2719 2720 public String getParamName() { 2721 return myParamName; 2722 } 2723 2724 public String getParamValue() { 2725 return myParamValue; 2726 } 2727 } 2728 2729 private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) { 2730 if (!params.containsKey(parameterName)) { 2731 params.put(parameterName, new ArrayList<>()); 2732 } 2733 params.get(parameterName).add(parameterValue); 2734 } 2735 2736 private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) { 2737 if (thePrefer != null) { 2738 theInvocation.addHeader( 2739 Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue()); 2740 } 2741 } 2742 2743 private static String validateAndEscapeConditionalUrl(String theSearchUrl) { 2744 Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null"); 2745 StringBuilder b = new StringBuilder(); 2746 boolean haveHadQuestionMark = false; 2747 for (int i = 0; i < theSearchUrl.length(); i++) { 2748 char nextChar = theSearchUrl.charAt(i); 2749 if (!haveHadQuestionMark) { 2750 if (nextChar == '?') { 2751 haveHadQuestionMark = true; 2752 } else if (!Character.isLetter(nextChar)) { 2753 throw new IllegalArgumentException(Msg.code(1402) 2754 + "Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " 2755 + theSearchUrl); 2756 } 2757 b.append(nextChar); 2758 } else { 2759 switch (nextChar) { 2760 case '|': 2761 case '?': 2762 case '$': 2763 case ':': 2764 b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar))); 2765 break; 2766 default: 2767 b.append(nextChar); 2768 break; 2769 } 2770 } 2771 } 2772 return b.toString(); 2773 } 2774}