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