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