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