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