001package ca.uhn.fhir.rest.client.impl; 002 003/* 004 * #%L 005 * HAPI FHIR - Client Framework 006 * %% 007 * Copyright (C) 2014 - 2022 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.i18n.Msg; 024import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 025import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 026import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 027import ca.uhn.fhir.context.FhirContext; 028import ca.uhn.fhir.context.FhirVersionEnum; 029import ca.uhn.fhir.context.IRuntimeDatatypeDefinition; 030import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.setResponseHeaders(theHeaders); 1518 return retVal; 1519 } 1520 } 1521 1522 private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> { 1523 private PreferReturnEnum myPrefer; 1524 1525 private OutcomeResponseHandler() { 1526 super(); 1527 } 1528 1529 private OutcomeResponseHandler(PreferReturnEnum thePrefer) { 1530 this(); 1531 myPrefer = thePrefer; 1532 } 1533 1534 @Override 1535 public MethodOutcome invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException { 1536 MethodOutcome response = MethodUtil.process2xxResponse(myContext, theResponseStatusCode, theResponseMimeType, theResponseInputStream, theHeaders); 1537 response.setCreatedUsingStatusCode(theResponseStatusCode); 1538 1539 if (myPrefer == PreferReturnEnum.REPRESENTATION) { 1540 if (response.getResource() == null) { 1541 if (response.getId() != null && isNotBlank(response.getId().getValue()) && response.getId().hasBaseUrl()) { 1542 ourLog.info("Server did not return resource for Prefer-representation, going to fetch: {}", response.getId().getValue()); 1543 IBaseResource resource = read().resource(response.getId().getResourceType()).withUrl(response.getId()).execute(); 1544 response.setResource(resource); 1545 } 1546 } 1547 } 1548 1549 response.setResponseHeaders(theHeaders); 1550 1551 return response; 1552 } 1553 } 1554 1555 private class PatchInternal extends BaseSearch<IPatchExecutable, IPatchWithQueryTyped, MethodOutcome> implements IPatch, IPatchWithBody, IPatchExecutable, IPatchWithQuery, IPatchWithQueryTyped { 1556 1557 private boolean myConditional; 1558 private IIdType myId; 1559 private String myPatchBody; 1560 private PatchTypeEnum myPatchType; 1561 private PreferReturnEnum myPrefer; 1562 private String myResourceType; 1563 private String mySearchUrl; 1564 1565 @Override 1566 public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) { 1567 Validate.notNull(theClass, "theClass must not be null"); 1568 String resourceType = myContext.getResourceType(theClass); 1569 return conditional(resourceType); 1570 } 1571 1572 @Override 1573 public IPatchWithQuery conditional(String theResourceType) { 1574 Validate.notBlank(theResourceType, "theResourceType must not be null"); 1575 myResourceType = theResourceType; 1576 myConditional = true; 1577 return this; 1578 } 1579 1580 // TODO: This is not longer used.. Deprecate it or just remove it? 1581 @Override 1582 public IPatchWithBody conditionalByUrl(String theSearchUrl) { 1583 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 1584 return this; 1585 } 1586 1587 @Override 1588 public MethodOutcome execute() { 1589 1590 if (myPatchType == null) { 1591 throw new InvalidRequestException(Msg.code(1383) + "No patch type supplied, cannot invoke server"); 1592 } 1593 if (myPatchBody == null) { 1594 throw new InvalidRequestException(Msg.code(1384) + "No patch body supplied, cannot invoke server"); 1595 } 1596 1597 BaseHttpClientInvocation invocation; 1598 if (isNotBlank(mySearchUrl)) { 1599 invocation = MethodUtil.createPatchInvocation(myContext, mySearchUrl, myPatchType, myPatchBody); 1600 } else if (myConditional) { 1601 invocation = MethodUtil.createPatchInvocation(myContext, myPatchType, myPatchBody, myResourceType, getParamMap()); 1602 } else { 1603 if (myId == null || myId.hasIdPart() == false) { 1604 throw new InvalidRequestException(Msg.code(1385) + "No ID supplied for resource to patch, can not invoke server"); 1605 } 1606 invocation = MethodUtil.createPatchInvocation(myContext, myId, myPatchType, myPatchBody); 1607 } 1608 1609 addPreferHeader(myPrefer, invocation); 1610 1611 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 1612 1613 Map<String, List<String>> params = new HashMap<>(); 1614 return invoke(params, binding, invocation); 1615 1616 } 1617 1618 @Override 1619 public IPatchExecutable prefer(PreferReturnEnum theReturn) { 1620 myPrefer = theReturn; 1621 return this; 1622 } 1623 1624 @Override 1625 public IPatchWithBody withBody(String thePatchBody) { 1626 Validate.notBlank(thePatchBody, "thePatchBody must not be blank"); 1627 1628 myPatchBody = thePatchBody; 1629 1630 EncodingEnum encoding = EncodingEnum.detectEncodingNoDefault(thePatchBody); 1631 if (encoding == EncodingEnum.XML) { 1632 myPatchType = PatchTypeEnum.XML_PATCH; 1633 } else if (encoding == EncodingEnum.JSON) { 1634 myPatchType = PatchTypeEnum.JSON_PATCH; 1635 } else { 1636 throw new IllegalArgumentException(Msg.code(1386) + "Unable to determine encoding of patch"); 1637 } 1638 1639 return this; 1640 } 1641 1642 @Override 1643 public IPatchWithBody withFhirPatch(IBaseParameters thePatchBody) { 1644 Validate.notNull(thePatchBody, "thePatchBody must not be null"); 1645 1646 myPatchType = PatchTypeEnum.FHIR_PATCH_JSON; 1647 myPatchBody = myContext.newJsonParser().encodeResourceToString(thePatchBody); 1648 1649 return this; 1650 } 1651 1652 @Override 1653 public IPatchExecutable withId(IIdType theId) { 1654 if (theId == null) { 1655 throw new NullPointerException(Msg.code(1387) + "theId can not be null"); 1656 } 1657 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())); 1658 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())); 1659 myId = theId; 1660 return this; 1661 } 1662 1663 @Override 1664 public IPatchExecutable withId(String theId) { 1665 if (theId == null) { 1666 throw new NullPointerException(Msg.code(1388) + "theId can not be null"); 1667 } 1668 return withId(new IdDt(theId)); 1669 } 1670 1671 } 1672 1673 @SuppressWarnings({"rawtypes", "unchecked"}) 1674 private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable { 1675 private IIdType myId; 1676 private String myIfVersionMatches; 1677 private ICallable myNotModifiedHandler; 1678 private RuntimeResourceDefinition myType; 1679 1680 @Override 1681 public Object execute() {// AAA 1682 if (myId.hasVersionIdPart()) { 1683 return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues); 1684 } 1685 return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements(), getCustomAcceptHeaderValue(), myCustomHeaderValues); 1686 } 1687 1688 @Override 1689 public IReadIfNoneMatch ifVersionMatches(String theVersion) { 1690 myIfVersionMatches = theVersion; 1691 return new IReadIfNoneMatch() { 1692 1693 @Override 1694 public IReadExecutable returnNull() { 1695 myNotModifiedHandler = new ICallable() { 1696 @Override 1697 public Object call() { 1698 return null; 1699 } 1700 }; 1701 return ReadInternal.this; 1702 } 1703 1704 @Override 1705 public IReadExecutable returnResource(final IBaseResource theInstance) { 1706 myNotModifiedHandler = new ICallable() { 1707 @Override 1708 public Object call() { 1709 return theInstance; 1710 } 1711 }; 1712 return ReadInternal.this; 1713 } 1714 1715 @Override 1716 public IReadExecutable throwNotModifiedException() { 1717 myNotModifiedHandler = null; 1718 return ReadInternal.this; 1719 } 1720 }; 1721 } 1722 1723 private void processUrl() { 1724 String resourceType = myId.getResourceType(); 1725 if (isBlank(resourceType)) { 1726 throw new IllegalArgumentException(Msg.code(1389) + myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId)); 1727 } 1728 myType = myContext.getResourceDefinition(resourceType); 1729 if (myType == null) { 1730 throw new IllegalArgumentException(Msg.code(1390) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId)); 1731 } 1732 } 1733 1734 @Override 1735 public <T extends IBaseResource> IReadTyped<T> resource(Class<T> theResourceType) { 1736 Validate.notNull(theResourceType, "theResourceType must not be null"); 1737 myType = myContext.getResourceDefinition(theResourceType); 1738 if (myType == null) { 1739 throw new IllegalArgumentException(Msg.code(1391) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); 1740 } 1741 return this; 1742 } 1743 1744 @Override 1745 public IReadTyped<IBaseResource> resource(String theResourceAsText) { 1746 Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText"); 1747 myType = myContext.getResourceDefinition(theResourceAsText); 1748 if (myType == null) { 1749 throw new IllegalArgumentException(Msg.code(1392) + myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText)); 1750 } 1751 return this; 1752 } 1753 1754 @Override 1755 public IReadExecutable withId(IIdType theId) { 1756 Validate.notNull(theId, "The ID can not be null"); 1757 Validate.notBlank(theId.getIdPart(), "The ID can not be blank"); 1758 myId = theId.toUnqualified(); 1759 return this; 1760 } 1761 1762 @Override 1763 public IReadExecutable withId(Long theId) { 1764 Validate.notNull(theId, "The ID can not be null"); 1765 myId = new IdDt(myType.getName(), theId); 1766 return this; 1767 } 1768 1769 @Override 1770 public IReadExecutable withId(String theId) { 1771 Validate.notBlank(theId, "The ID can not be blank"); 1772 if (theId.indexOf('/') == -1) { 1773 myId = new IdDt(myType.getName(), theId); 1774 } else { 1775 myId = new IdDt(theId); 1776 } 1777 return this; 1778 } 1779 1780 @Override 1781 public IReadExecutable withIdAndVersion(String theId, String theVersion) { 1782 Validate.notBlank(theId, "The ID can not be blank"); 1783 myId = new IdDt(myType.getName(), theId, theVersion); 1784 return this; 1785 } 1786 1787 @Override 1788 public IReadExecutable withUrl(IIdType theUrl) { 1789 Validate.notNull(theUrl, "theUrl can not be null"); 1790 myId = theUrl; 1791 processUrl(); 1792 return this; 1793 } 1794 1795 @Override 1796 public IReadExecutable withUrl(String theUrl) { 1797 myId = new IdDt(theUrl); 1798 processUrl(); 1799 return this; 1800 } 1801 1802 } 1803 1804 private final class ResourceListResponseHandler implements IClientResponseHandler<List<IBaseResource>> { 1805 1806 @SuppressWarnings("unchecked") 1807 @Override 1808 public List<IBaseResource> invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) 1809 throws BaseServerResponseException { 1810 Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass(); 1811 ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<>((Class<IBaseResource>) bundleType); 1812 IBaseResource response = handler.invokeClient(theResponseMimeType, theResponseInputStream, theResponseStatusCode, theHeaders); 1813 IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); 1814 bundleFactory.initializeWithBundleResource(response); 1815 return bundleFactory.toListOfResources(); 1816 } 1817 } 1818 1819 @SuppressWarnings({"rawtypes", "unchecked"}) 1820 private class SearchInternal<OUTPUT> extends BaseSearch<IQuery<OUTPUT>, IQuery<OUTPUT>, OUTPUT> implements IQuery<OUTPUT>, IUntypedQuery<IQuery<OUTPUT>> { 1821 1822 private String myCompartmentName; 1823 private List<Include> myInclude = new ArrayList<>(); 1824 private DateRangeParam myLastUpdated; 1825 private Integer myParamLimit; 1826 private Integer myParamOffset; 1827 private List<Collection<String>> myProfiles = new ArrayList<>(); 1828 private String myResourceId; 1829 private String myResourceName; 1830 private Class<? extends IBaseResource> myResourceType; 1831 private Class<? extends IBaseBundle> myReturnBundleType; 1832 private List<Include> myRevInclude = new ArrayList<>(); 1833 private SearchStyleEnum mySearchStyle; 1834 private String mySearchUrl; 1835 private List<TokenParam> mySecurity = new ArrayList<>(); 1836 private List<SortInternal> mySort = new ArrayList<>(); 1837 private List<TokenParam> myTags = new ArrayList<>(); 1838 private SearchTotalModeEnum myTotalMode; 1839 1840 public SearchInternal() { 1841 myResourceType = null; 1842 myResourceName = null; 1843 mySearchUrl = null; 1844 } 1845 1846 @Override 1847 public IQuery byUrl(String theSearchUrl) { 1848 Validate.notBlank(theSearchUrl, "theSearchUrl must not be blank/null"); 1849 1850 if (theSearchUrl.startsWith("http://") || theSearchUrl.startsWith("https://")) { 1851 mySearchUrl = theSearchUrl; 1852 int qIndex = mySearchUrl.indexOf('?'); 1853 if (qIndex != -1) { 1854 mySearchUrl = mySearchUrl.substring(0, qIndex) + validateAndEscapeConditionalUrl(mySearchUrl.substring(qIndex)); 1855 } 1856 } else { 1857 String searchUrl = theSearchUrl; 1858 if (searchUrl.startsWith("/")) { 1859 searchUrl = searchUrl.substring(1); 1860 } 1861 if (!searchUrl.matches("[a-zA-Z]+($|\\?.*)")) { 1862 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]"); 1863 } 1864 int qIndex = searchUrl.indexOf('?'); 1865 if (qIndex == -1) { 1866 mySearchUrl = getUrlBase() + '/' + searchUrl; 1867 } else { 1868 mySearchUrl = getUrlBase() + '/' + validateAndEscapeConditionalUrl(searchUrl); 1869 } 1870 } 1871 return this; 1872 } 1873 1874 @Override 1875 public IQuery count(int theLimitTo) { 1876 if (theLimitTo > 0) { 1877 myParamLimit = theLimitTo; 1878 } else { 1879 myParamLimit = null; 1880 } 1881 return this; 1882 } 1883 1884 @Override 1885 public IQuery offset(int theOffset) { 1886 if (theOffset >= 0) { 1887 myParamOffset = theOffset; 1888 } else { 1889 myParamOffset = null; 1890 } 1891 return this; 1892 } 1893 1894 @Override 1895 public OUTPUT execute() { 1896 1897 Map<String, List<String>> params = getParamMap(); 1898 1899 for (TokenParam next : myTags) { 1900 addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken(myContext)); 1901 } 1902 1903 for (TokenParam next : mySecurity) { 1904 addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken(myContext)); 1905 } 1906 1907 for (Collection<String> profileUris : myProfiles) { 1908 StringBuilder builder = new StringBuilder(); 1909 for (Iterator<String> profileItr = profileUris.iterator(); profileItr.hasNext(); ) { 1910 builder.append(profileItr.next()); 1911 if (profileItr.hasNext()) { 1912 builder.append(','); 1913 } 1914 } 1915 addParam(params, Constants.PARAM_PROFILE, builder.toString()); 1916 } 1917 1918 for (Include next : myInclude) { 1919 if (next.isRecurse()) { 1920 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 1921 addParam(params, Constants.PARAM_INCLUDE_ITERATE, next.getValue()); 1922 } else { 1923 addParam(params, Constants.PARAM_INCLUDE_RECURSE, next.getValue()); 1924 } 1925 } else { 1926 addParam(params, Constants.PARAM_INCLUDE, next.getValue()); 1927 } 1928 } 1929 1930 for (Include next : myRevInclude) { 1931 if (next.isRecurse()) { 1932 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 1933 addParam(params, Constants.PARAM_REVINCLUDE_ITERATE, next.getValue()); 1934 } else { 1935 addParam(params, Constants.PARAM_REVINCLUDE_RECURSE, next.getValue()); 1936 } 1937 } else { 1938 addParam(params, Constants.PARAM_REVINCLUDE, next.getValue()); 1939 } 1940 } 1941 1942 if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2)) { 1943 SortSpec rootSs = null; 1944 SortSpec lastSs = null; 1945 for (SortInternal next : mySort) { 1946 SortSpec nextSortSpec = new SortSpec(); 1947 nextSortSpec.setParamName(next.getParamValue()); 1948 nextSortSpec.setOrder(next.getDirection()); 1949 if (rootSs == null) { 1950 rootSs = nextSortSpec; 1951 } else { 1952 lastSs.setChain(nextSortSpec); 1953 } 1954 lastSs = nextSortSpec; 1955 } 1956 if (rootSs != null) { 1957 addParam(params, Constants.PARAM_SORT, SortParameter.createSortStringDstu3(rootSs)); 1958 } 1959 } else { 1960 for (SortInternal next : mySort) { 1961 addParam(params, next.getParamName(), next.getParamValue()); 1962 } 1963 } 1964 1965 if (myParamLimit != null) { 1966 addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit)); 1967 } 1968 1969 if (myParamOffset != null) { 1970 addParam(params, Constants.PARAM_OFFSET, Integer.toString(myParamOffset)); 1971 } 1972 1973 if (myLastUpdated != null) { 1974 for (DateParam next : myLastUpdated.getValuesAsQueryTokens()) { 1975 addParam(params, Constants.PARAM_LASTUPDATED, next.getValueAsQueryToken(myContext)); 1976 } 1977 } 1978 1979 if (myTotalMode != null) { 1980 addParam(params, Constants.PARAM_SEARCH_TOTAL_MODE, myTotalMode.getCode()); 1981 } 1982 1983 IClientResponseHandler<? extends IBase> binding; 1984 binding = new ResourceResponseHandler(myReturnBundleType, getPreferResponseTypes(myResourceType)); 1985 1986 IdDt resourceId = myResourceId != null ? new IdDt(myResourceId) : null; 1987 1988 BaseHttpClientInvocation invocation; 1989 if (mySearchUrl != null) { 1990 invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, UrlSourceEnum.EXPLICIT, params); 1991 } else { 1992 invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle); 1993 } 1994 1995 return (OUTPUT) invoke(params, binding, invocation); 1996 1997 } 1998 1999 @Override 2000 public IQuery forAllResources() { 2001 return this; 2002 } 2003 2004 @Override 2005 public IQuery forResource(Class theResourceType) { 2006 setType(theResourceType); 2007 return this; 2008 } 2009 2010 @Override 2011 public IQuery forResource(String theResourceName) { 2012 setType(theResourceName); 2013 return this; 2014 } 2015 2016 @Override 2017 public IQuery include(Include theInclude) { 2018 myInclude.add(theInclude); 2019 return this; 2020 } 2021 2022 @Override 2023 public IQuery lastUpdated(DateRangeParam theLastUpdated) { 2024 myLastUpdated = theLastUpdated; 2025 return this; 2026 } 2027 2028 @Deprecated // override deprecated method 2029 @Override 2030 public IQuery limitTo(int theLimitTo) { 2031 return count(theLimitTo); 2032 } 2033 2034 @Override 2035 public IQuery<OUTPUT> totalMode(SearchTotalModeEnum theSearchTotalModeEnum) { 2036 myTotalMode = theSearchTotalModeEnum; 2037 return this; 2038 } 2039 2040 @Override 2041 public IQuery returnBundle(Class theClass) { 2042 if (theClass == null) { 2043 throw new NullPointerException(Msg.code(1394) + "theClass must not be null"); 2044 } 2045 myReturnBundleType = theClass; 2046 return this; 2047 } 2048 2049 @Override 2050 public IQuery revInclude(Include theInclude) { 2051 myRevInclude.add(theInclude); 2052 return this; 2053 } 2054 2055 private void setType(Class<? extends IBaseResource> theResourceType) { 2056 myResourceType = theResourceType; 2057 RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType); 2058 myResourceName = definition.getName(); 2059 } 2060 2061 private void setType(String theResourceName) { 2062 myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass(); 2063 myResourceName = theResourceName; 2064 } 2065 2066 @Override 2067 public ISort sort() { 2068 SortInternal retVal = new SortInternal(this); 2069 mySort.add(retVal); 2070 return retVal; 2071 } 2072 2073 @Override 2074 public IQuery sort(SortSpec theSortSpec) { 2075 SortSpec sortSpec = theSortSpec; 2076 while (sortSpec != null) { 2077 mySort.add(new SortInternal(sortSpec)); 2078 sortSpec = sortSpec.getChain(); 2079 } 2080 return this; 2081 } 2082 2083 @Override 2084 public IQuery usingStyle(SearchStyleEnum theStyle) { 2085 mySearchStyle = theStyle; 2086 return this; 2087 } 2088 2089 @Override 2090 public IQuery withAnyProfile(Collection theProfileUris) { 2091 Validate.notEmpty(theProfileUris, "theProfileUris must not be null or empty"); 2092 myProfiles.add(theProfileUris); 2093 return this; 2094 } 2095 2096 @Override 2097 public IQuery withIdAndCompartment(String theResourceId, String theCompartmentName) { 2098 myResourceId = theResourceId; 2099 myCompartmentName = theCompartmentName; 2100 return this; 2101 } 2102 2103 @Override 2104 public IQuery withProfile(String theProfileUri) { 2105 Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty"); 2106 myProfiles.add(Collections.singletonList(theProfileUri)); 2107 return this; 2108 } 2109 2110 @Override 2111 public IQuery withSecurity(String theSystem, String theCode) { 2112 Validate.notBlank(theCode, "theCode must not be null or empty"); 2113 mySecurity.add(new TokenParam(theSystem, theCode)); 2114 return this; 2115 } 2116 2117 @Override 2118 public IQuery withTag(String theSystem, String theCode) { 2119 Validate.notBlank(theCode, "theCode must not be null or empty"); 2120 myTags.add(new TokenParam(theSystem, theCode)); 2121 return this; 2122 } 2123 2124 } 2125 2126 private final class StringResponseHandler implements IClientResponseHandler<String> { 2127 2128 @Override 2129 public String invokeClient(String theResponseMimeType, InputStream theResponseInputStream, int theResponseStatusCode, Map<String, List<String>> theHeaders) 2130 throws IOException, BaseServerResponseException { 2131 return IOUtils.toString(theResponseInputStream, Charsets.UTF_8); 2132 } 2133 } 2134 2135 private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T> { 2136 2137 private IBaseBundle myBaseBundle; 2138 private String myRawBundle; 2139 private EncodingEnum myRawBundleEncoding; 2140 private List<? extends IBaseResource> myResources; 2141 2142 public TransactionExecutable(IBaseBundle theBundle) { 2143 myBaseBundle = theBundle; 2144 } 2145 2146 public TransactionExecutable(List<? extends IBaseResource> theResources) { 2147 myResources = theResources; 2148 } 2149 2150 public TransactionExecutable(String theBundle) { 2151 myRawBundle = theBundle; 2152 myRawBundleEncoding = EncodingEnum.detectEncodingNoDefault(myRawBundle); 2153 if (myRawBundleEncoding == null) { 2154 throw new IllegalArgumentException(Msg.code(1395) + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 2155 } 2156 } 2157 2158 @SuppressWarnings({"unchecked", "rawtypes"}) 2159 @Override 2160 public T execute() { 2161 Map<String, List<String>> params = new HashMap<String, List<String>>(); 2162 if (myResources != null) { 2163 ResourceListResponseHandler binding = new ResourceListResponseHandler(); 2164 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext); 2165 return (T) invoke(params, binding, invocation); 2166 } else if (myBaseBundle != null) { 2167 ResourceResponseHandler binding = new ResourceResponseHandler(myBaseBundle.getClass(), getPreferResponseTypes()); 2168 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBaseBundle, myContext); 2169 return (T) invoke(params, binding, invocation); 2170 // } else if (myRawBundle != null) { 2171 } else { 2172 StringResponseHandler binding = new StringResponseHandler(); 2173 /* 2174 * If the user has explicitly requested a given encoding, we may need to re-encode the raw string 2175 */ 2176 if (getParamEncoding() != null) { 2177 if (EncodingEnum.detectEncodingNoDefault(myRawBundle) != getParamEncoding()) { 2178 IBaseResource parsed = parseResourceBody(myRawBundle); 2179 myRawBundle = getParamEncoding().newParser(getFhirContext()).encodeResourceToString(parsed); 2180 } 2181 } 2182 BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myRawBundle, myContext); 2183 return (T) invoke(params, binding, invocation); 2184 } 2185 } 2186 2187 } 2188 2189 private final class TransactionInternal implements ITransaction { 2190 2191 @Override 2192 public ITransactionTyped<String> withBundle(String theBundle) { 2193 Validate.notBlank(theBundle, "theBundle must not be null"); 2194 return new TransactionExecutable<String>(theBundle); 2195 } 2196 2197 @Override 2198 public <T extends IBaseBundle> ITransactionTyped<T> withBundle(T theBundle) { 2199 Validate.notNull(theBundle, "theBundle must not be null"); 2200 return new TransactionExecutable<T>(theBundle); 2201 } 2202 2203 @Override 2204 public ITransactionTyped<List<IBaseResource>> withResources(List<? extends IBaseResource> theResources) { 2205 Validate.notNull(theResources, "theResources must not be null"); 2206 2207 for (IBaseResource next : theResources) { 2208 String entryMethod = null; 2209 if (next instanceof IResource) { 2210 BundleEntryTransactionMethodEnum entryMethodEnum = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) next); 2211 if (entryMethodEnum != null) { 2212 entryMethod = entryMethodEnum.getCode(); 2213 } 2214 } else { 2215 entryMethod = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IAnyResource) next); 2216 } 2217 2218 if (isBlank(entryMethod)) { 2219 if (isBlank(next.getIdElement().getValue())) { 2220 entryMethod = "POST"; 2221 } else { 2222 entryMethod = "PUT"; 2223 } 2224 if (next instanceof IResource) { 2225 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put((IResource) next, BundleEntryTransactionMethodEnum.valueOf(entryMethod)); 2226 } else { 2227 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put((IAnyResource) next, entryMethod); 2228 } 2229 2230 } 2231 2232 } 2233 2234 return new TransactionExecutable<>(theResources); 2235 } 2236 2237 } 2238 2239 private class UpdateInternal extends BaseSearch<IUpdateExecutable, IUpdateWithQueryTyped, MethodOutcome> 2240 implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { 2241 2242 private boolean myConditional; 2243 private IIdType myId; 2244 private PreferReturnEnum myPrefer; 2245 private IBaseResource myResource; 2246 private String myResourceBody; 2247 private String mySearchUrl; 2248 2249 @Override 2250 public IUpdateWithQuery conditional() { 2251 myConditional = true; 2252 return this; 2253 } 2254 2255 @Override 2256 public IUpdateTyped conditionalByUrl(String theSearchUrl) { 2257 mySearchUrl = validateAndEscapeConditionalUrl(theSearchUrl); 2258 return this; 2259 } 2260 2261 @Override 2262 public MethodOutcome execute() { 2263 if (myResource == null) { 2264 myResource = parseResourceBody(myResourceBody); 2265 } 2266 2267 // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding 2268 if (getParamEncoding() != null) { 2269 myResourceBody = null; 2270 } 2271 2272 BaseHttpClientInvocation invocation; 2273 if (mySearchUrl != null) { 2274 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl); 2275 } else if (myConditional) { 2276 invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, getParamMap()); 2277 } else { 2278 if (myId == null) { 2279 myId = myResource.getIdElement(); 2280 } 2281 2282 if (myId == null || myId.hasIdPart() == false) { 2283 throw new InvalidRequestException(Msg.code(1396) + "No ID supplied for resource to update, can not invoke server"); 2284 } 2285 invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); 2286 } 2287 2288 addPreferHeader(myPrefer, invocation); 2289 2290 OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 2291 2292 Map<String, List<String>> params = new HashMap<String, List<String>>(); 2293 return invoke(params, binding, invocation); 2294 2295 } 2296 2297 @Override 2298 public IUpdateExecutable prefer(PreferReturnEnum theReturn) { 2299 myPrefer = theReturn; 2300 return this; 2301 } 2302 2303 @Override 2304 public IUpdateTyped resource(IBaseResource theResource) { 2305 Validate.notNull(theResource, "Resource can not be null"); 2306 myResource = theResource; 2307 return this; 2308 } 2309 2310 @Override 2311 public IUpdateTyped resource(String theResourceBody) { 2312 Validate.notBlank(theResourceBody, "Body can not be null or blank"); 2313 myResourceBody = theResourceBody; 2314 return this; 2315 } 2316 2317 @Override 2318 public IUpdateExecutable withId(IIdType theId) { 2319 if (theId == null) { 2320 throw new NullPointerException(Msg.code(1397) + "theId can not be null"); 2321 } 2322 if (theId.hasIdPart() == false) { 2323 throw new NullPointerException(Msg.code(1398) + "theId must not be blank and must contain an ID, found: " + theId.getValue()); 2324 } 2325 myId = theId; 2326 return this; 2327 } 2328 2329 @Override 2330 public IUpdateExecutable withId(String theId) { 2331 if (theId == null) { 2332 throw new NullPointerException(Msg.code(1399) + "theId can not be null"); 2333 } 2334 if (isBlank(theId)) { 2335 throw new NullPointerException(Msg.code(1400) + "theId must not be blank and must contain an ID, found: " + theId); 2336 } 2337 myId = new IdDt(theId); 2338 return this; 2339 } 2340 2341 } 2342 2343 private class ValidateInternal extends BaseClientExecutable<IValidateUntyped, MethodOutcome> implements IValidate, IValidateUntyped { 2344 private IBaseResource myResource; 2345 2346 @Override 2347 public MethodOutcome execute() { 2348 BaseHttpClientInvocation invocation = ValidateMethodBindingDstu2Plus.createValidateInvocation(myContext, myResource); 2349 ResourceResponseHandler<BaseOperationOutcome> handler = new ResourceResponseHandler<BaseOperationOutcome>(null, null); 2350 IBaseOperationOutcome outcome = invoke(null, handler, invocation); 2351 MethodOutcome retVal = new MethodOutcome(); 2352 retVal.setOperationOutcome(outcome); 2353 return retVal; 2354 } 2355 2356 @Override 2357 public IValidateUntyped resource(IBaseResource theResource) { 2358 Validate.notNull(theResource, "theResource must not be null"); 2359 myResource = theResource; 2360 return this; 2361 } 2362 2363 @Override 2364 public IValidateUntyped resource(String theResourceRaw) { 2365 Validate.notBlank(theResourceRaw, "theResourceRaw must not be null or blank"); 2366 myResource = parseResourceBody(theResourceRaw); 2367 2368 EncodingEnum enc = EncodingEnum.detectEncodingNoDefault(theResourceRaw); 2369 if (enc == null) { 2370 throw new IllegalArgumentException(Msg.code(1401) + myContext.getLocalizer().getMessage(GenericClient.class, "cantDetermineRequestType")); 2371 } 2372 switch (enc) { 2373 case XML: 2374 encodedXml(); 2375 break; 2376 case JSON: 2377 encodedJson(); 2378 break; 2379 } 2380 return this; 2381 } 2382 2383 } 2384 2385 @SuppressWarnings("rawtypes") 2386 private static class SortInternal implements ISort { 2387 2388 private SortOrderEnum myDirection; 2389 private SearchInternal myFor; 2390 private String myParamName; 2391 private String myParamValue; 2392 2393 public SortInternal(SearchInternal theFor) { 2394 myFor = theFor; 2395 } 2396 2397 public SortInternal(SortSpec theSortSpec) { 2398 if (theSortSpec.getOrder() == null) { 2399 myParamName = Constants.PARAM_SORT; 2400 } else if (theSortSpec.getOrder() == SortOrderEnum.ASC) { 2401 myParamName = Constants.PARAM_SORT_ASC; 2402 } else if (theSortSpec.getOrder() == SortOrderEnum.DESC) { 2403 myParamName = Constants.PARAM_SORT_DESC; 2404 } 2405 myDirection = theSortSpec.getOrder(); 2406 myParamValue = theSortSpec.getParamName(); 2407 } 2408 2409 @Override 2410 public IQuery ascending(IParam theParam) { 2411 myParamName = Constants.PARAM_SORT_ASC; 2412 myDirection = SortOrderEnum.ASC; 2413 myParamValue = theParam.getParamName(); 2414 return myFor; 2415 } 2416 2417 @Override 2418 public IQuery ascending(String theParam) { 2419 myParamName = Constants.PARAM_SORT_ASC; 2420 myDirection = SortOrderEnum.ASC; 2421 myParamValue = theParam; 2422 return myFor; 2423 } 2424 2425 @Override 2426 public IQuery defaultOrder(IParam theParam) { 2427 myParamName = Constants.PARAM_SORT; 2428 myDirection = null; 2429 myParamValue = theParam.getParamName(); 2430 return myFor; 2431 } 2432 2433 @Override 2434 public IQuery defaultOrder(String theParam) { 2435 myParamName = Constants.PARAM_SORT; 2436 myDirection = null; 2437 myParamValue = theParam; 2438 return myFor; 2439 } 2440 2441 @Override 2442 public IQuery descending(IParam theParam) { 2443 myParamName = Constants.PARAM_SORT_DESC; 2444 myDirection = SortOrderEnum.DESC; 2445 myParamValue = theParam.getParamName(); 2446 return myFor; 2447 } 2448 2449 @Override 2450 public IQuery descending(String theParam) { 2451 myParamName = Constants.PARAM_SORT_DESC; 2452 myDirection = SortOrderEnum.DESC; 2453 myParamValue = theParam; 2454 return myFor; 2455 } 2456 2457 public SortOrderEnum getDirection() { 2458 return myDirection; 2459 } 2460 2461 public String getParamName() { 2462 return myParamName; 2463 } 2464 2465 public String getParamValue() { 2466 return myParamValue; 2467 } 2468 2469 } 2470 2471 private static void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) { 2472 if (!params.containsKey(parameterName)) { 2473 params.put(parameterName, new ArrayList<String>()); 2474 } 2475 params.get(parameterName).add(parameterValue); 2476 } 2477 2478 private static void addPreferHeader(PreferReturnEnum thePrefer, BaseHttpClientInvocation theInvocation) { 2479 if (thePrefer != null) { 2480 theInvocation.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + '=' + thePrefer.getHeaderValue()); 2481 } 2482 } 2483 2484 private static String validateAndEscapeConditionalUrl(String theSearchUrl) { 2485 Validate.notBlank(theSearchUrl, "Conditional URL can not be blank/null"); 2486 StringBuilder b = new StringBuilder(); 2487 boolean haveHadQuestionMark = false; 2488 for (int i = 0; i < theSearchUrl.length(); i++) { 2489 char nextChar = theSearchUrl.charAt(i); 2490 if (!haveHadQuestionMark) { 2491 if (nextChar == '?') { 2492 haveHadQuestionMark = true; 2493 } else if (!Character.isLetter(nextChar)) { 2494 throw new IllegalArgumentException(Msg.code(1402) + "Conditional URL must be in the format \"[ResourceType]?[Params]\" and must not have a base URL - Found: " + theSearchUrl); 2495 } 2496 b.append(nextChar); 2497 } else { 2498 switch (nextChar) { 2499 case '|': 2500 case '?': 2501 case '$': 2502 case ':': 2503 b.append(UrlUtil.escapeUrlParam(Character.toString(nextChar))); 2504 break; 2505 default: 2506 b.append(nextChar); 2507 break; 2508 } 2509 } 2510 } 2511 return b.toString(); 2512 } 2513 2514}