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