001package ball.http; 002/*- 003 * ########################################################################## 004 * Web API Client (HTTP) Utilities 005 * $Id: ProtocolRequestBuilder.java 6118 2020-06-04 19:31:45Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-http/trunk/src/main/java/ball/http/ProtocolRequestBuilder.java $ 007 * %% 008 * Copyright (C) 2016 - 2020 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import ball.activation.ByteArrayDataSource; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.lang.annotation.Annotation; 029import java.lang.reflect.InvocationTargetException; 030import java.lang.reflect.Method; 031import java.lang.reflect.Parameter; 032import java.net.URI; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.Collections; 036import java.util.TreeMap; 037import java.util.Set; 038import java.util.stream.Collectors; 039import javax.ws.rs.ApplicationPath; 040import javax.ws.rs.BeanParam; 041import javax.ws.rs.ConstrainedTo; 042import javax.ws.rs.Consumes; 043import javax.ws.rs.CookieParam; 044/* import javax.ws.rs.DefaultValue; */ 045import javax.ws.rs.DELETE; 046/* import javax.ws.rs.Encoded; */ 047import javax.ws.rs.FormParam; 048import javax.ws.rs.GET; 049import javax.ws.rs.HEAD; 050import javax.ws.rs.HeaderParam; 051import javax.ws.rs.MatrixParam; 052import javax.ws.rs.OPTIONS; 053import javax.ws.rs.PATCH; 054import javax.ws.rs.POST; 055import javax.ws.rs.PUT; 056import javax.ws.rs.Path; 057import javax.ws.rs.PathParam; 058import javax.ws.rs.Produces; 059import javax.ws.rs.QueryParam; 060import javax.ws.rs.core.UriBuilder; 061import lombok.ToString; 062import org.apache.commons.lang3.ClassUtils; 063import org.apache.commons.lang3.reflect.MethodUtils; 064import org.apache.http.HttpEntity; 065import org.apache.http.HttpMessage; 066import org.apache.http.HttpRequest; 067import org.apache.http.NameValuePair; 068import org.apache.http.client.entity.UrlEncodedFormEntity; 069import org.apache.http.client.methods.HttpDelete; 070import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 071import org.apache.http.client.methods.HttpGet; 072import org.apache.http.client.methods.HttpHead; 073import org.apache.http.client.methods.HttpOptions; 074import org.apache.http.client.methods.HttpPatch; 075import org.apache.http.client.methods.HttpPost; 076import org.apache.http.client.methods.HttpPut; 077import org.apache.http.client.methods.HttpRequestBase; 078import org.apache.http.entity.AbstractHttpEntity; 079import org.apache.http.entity.ContentType; 080import org.apache.http.message.BasicNameValuePair; 081 082import static java.util.Objects.requireNonNull; 083import static org.apache.commons.lang3.StringUtils.EMPTY; 084import static org.apache.commons.lang3.StringUtils.isBlank; 085import static org.apache.commons.lang3.StringUtils.isNotBlank; 086 087/** 088 * <p> 089 * {@link HttpRequest} builder for {@link ProtocolClient#protocol()}. See 090 * the {@code type(Annotation,Class)}, {@code method(Annotation,Method)}, 091 * {@code parameter(Annotation,Parameter,...)}, 092 * and {@code parameter(Parameter,...)} methods for the supported protocol 093 * interface, method, and method parameter {@link Annotation}s and types. 094 * </p> 095 * <p> 096 * Protocol API authors should consider designing protocol methods to throw 097 * {@link org.apache.http.client.HttpResponseException}, 098 * {@link org.apache.http.client.ClientProtocolException}, and 099 * {@link java.io.IOException}. 100 * </p> 101 * <p> 102 * Supported type (interface) annotations: 103 * 104 * {@include #TYPE_ANNOTATIONS} 105 * </p> 106 * <p> 107 * Supported method annotations: 108 * 109 * {@include #METHOD_ANNOTATIONS} 110 * </p> 111 * <p> 112 * Supported method parameter annotations: 113 * 114 * {@include #PARAMETER_ANNOTATIONS} 115 * </p> 116 * <p> 117 * Supported method parameter types: 118 * 119 * {@include #PARAMETER_TYPES} 120 * </p> 121 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 122 * @version $Revision: 6118 $ 123 */ 124@ToString 125public class ProtocolRequestBuilder { 126 127 /** 128 * Supported type (interface) annotations. 129 */ 130 public static final Set<Class<? extends Annotation>> TYPE_ANNOTATIONS = 131 Arrays.stream(ProtocolRequestBuilder.class.getDeclaredMethods()) 132 .filter(t -> t.getName().equals("type")) 133 .filter(t -> t.getParameterCount() > 0) 134 .filter(t -> Annotation.class.isAssignableFrom(t.getParameterTypes()[0])) 135 .filter(t -> ClassUtils.isAssignable(new Class<?>[] { 136 t.getParameterTypes()[0], 137 Class.class 138 }, 139 t.getParameterTypes())) 140 .map(t -> t.getParameterTypes()[0].asSubclass(Annotation.class)) 141 .collect(Collectors.toSet()); 142 143 /** 144 * Supported method annotations. 145 */ 146 public static final Set<Class<? extends Annotation>> METHOD_ANNOTATIONS = 147 Arrays.stream(ProtocolRequestBuilder.class.getDeclaredMethods()) 148 .filter(t -> t.getName().equals("method")) 149 .filter(t -> t.getParameterCount() > 0) 150 .filter(t -> Annotation.class.isAssignableFrom(t.getParameterTypes()[0])) 151 .filter(t -> ClassUtils.isAssignable(new Class<?>[] { 152 t.getParameterTypes()[0], 153 Method.class 154 }, 155 t.getParameterTypes())) 156 .map(t -> t.getParameterTypes()[0].asSubclass(Annotation.class)) 157 .collect(Collectors.toSet()); 158 159 /** 160 * Supported method parameter annotations. 161 */ 162 public static final Set<Class<? extends Annotation>> PARAMETER_ANNOTATIONS = 163 Arrays.stream(ProtocolRequestBuilder.class.getDeclaredMethods()) 164 .filter(t -> t.getName().equals("parameter")) 165 .filter(t -> t.getParameterCount() > 0) 166 .filter(t -> Annotation.class.isAssignableFrom(t.getParameterTypes()[0])) 167 .filter(t -> ClassUtils.isAssignable(new Class<?>[] { t.getParameterTypes()[0], Parameter.class, null }, 168 t.getParameterTypes())) 169 .map(t -> t.getParameterTypes()[0].asSubclass(Annotation.class)) 170 .collect(Collectors.toSet()); 171 172 /** 173 * Supported method parameter types. 174 */ 175 public static final Set<Class<?>> PARAMETER_TYPES = 176 Arrays.stream(ProtocolRequestBuilder.class.getDeclaredMethods()) 177 .filter(t -> t.getName().equals("parameter")) 178 .filter(t -> t.getParameterCount() > 0) 179 .filter(t -> ClassUtils.isAssignable(new Class<?>[] { Parameter.class, null }, 180 t.getParameterTypes())) 181 .map(t -> t.getParameterTypes()[1]) 182 .collect(Collectors.toSet()); 183 184 private final ProtocolClient<?> client; 185 private transient HttpMessage request = null; 186 private transient UriBuilder uri = UriBuilder.fromUri(EMPTY); 187 private transient TreeMap<String,Object> templateValues = new TreeMap<>(); 188 private transient Object body = null; 189 190 /** 191 * Sole constructor. 192 * 193 * @param client The {@link ProtocolClient}. 194 */ 195 protected ProtocolRequestBuilder(ProtocolClient<?> client) { 196 this.client = requireNonNull(client, "client"); 197 } 198 199 /** 200 * Build a {@link HttpRequest} ({@link HttpMessage}) from the protocol 201 * interface {@link Method}. 202 * 203 * @param method The interface {@link Method}. 204 * @param argv The caller's arguments. 205 * 206 * @return The {@link HttpMessage}. 207 * 208 * @throws Throwable If the call fails for any reason. 209 */ 210 public HttpMessage build(Method method, Object[] argv) throws Throwable { 211 /* 212 * Process annotations and arguments 213 */ 214 process(method.getDeclaringClass(), method, argv); 215 /* 216 * URI 217 */ 218 if (request instanceof HttpRequestBase) { 219 ((HttpRequestBase) request) 220 .setURI(uri.resolveTemplates(templateValues).build()); 221 } 222 /* 223 * Body 224 */ 225 HttpEntity entity = null; 226 227 if (body instanceof HttpEntity) { 228 entity = (HttpEntity) body; 229 } else if (body instanceof Form) { 230 entity = 231 new UrlEncodedFormEntity((Form) body, client.getCharset()); 232 } else if (body != null) { 233 entity = new JSONHttpEntity(body); 234 } 235 236 if (entity != null) { 237 ((HttpEntityEnclosingRequestBase) request).setEntity(entity); 238 } 239 240 return request; 241 } 242 243 private void process(Class<?> type, 244 Method method, Object... argv) throws Throwable { 245 for (Annotation annotation : type.getAnnotations()) { 246 try { 247 if (TYPE_ANNOTATIONS.contains(annotation.annotationType())) { 248 invoke("type", 249 new Object[] { annotation, type }, 250 annotation.annotationType(), Class.class); 251 } 252 } catch (NoSuchMethodException exception) { 253 throw new IllegalStateException(String.valueOf(annotation.annotationType()), 254 exception); 255 } catch (IllegalAccessException exception) { 256 } catch (InvocationTargetException exception) { 257 throw exception.getTargetException(); 258 } 259 } 260 261 for (Annotation annotation : method.getAnnotations()) { 262 try { 263 if (METHOD_ANNOTATIONS.contains(annotation.annotationType())) { 264 invoke("method", 265 new Object[] { annotation, method }, 266 annotation.annotationType(), Method.class); 267 } 268 } catch (NoSuchMethodException exception) { 269 throw new IllegalStateException(String.valueOf(annotation.annotationType()), 270 exception); 271 } catch (IllegalAccessException exception) { 272 } catch (InvocationTargetException exception) { 273 throw exception.getTargetException(); 274 } 275 } 276 277 Parameter[] parameters = method.getParameters(); 278 279 for (int i = 0; i < parameters.length; i += 1) { 280 process(parameters[i], argv[i]); 281 } 282 } 283 284 private void process(Parameter parameter, 285 Object argument) throws Throwable { 286 Annotation[] annotations = parameter.getAnnotations(); 287 288 if (annotations.length > 0) { 289 for (int i = 0; i < annotations.length; i += 1) { 290 process(annotations[i], parameter, argument); 291 } 292 } else { 293 try { 294 invoke("parameter", 295 new Object[] { parameter, argument }, 296 Parameter.class, parameter.getType()); 297 } catch (NoSuchMethodException exception) { 298 } catch (IllegalAccessException exception) { 299 } catch (InvocationTargetException exception) { 300 throw exception.getTargetException(); 301 } 302 } 303 } 304 305 private void process(Annotation annotation, 306 Parameter parameter, 307 Object argument) throws Throwable { 308 try { 309 if (PARAMETER_ANNOTATIONS.contains(annotation.annotationType())) { 310 invoke("parameter", 311 new Object[] { annotation, parameter, argument }, 312 annotation.annotationType(), Parameter.class, parameter.getType()); 313 } 314 } catch (NoSuchMethodException exception) { 315 throw new IllegalStateException(String.valueOf(annotation.annotationType()), 316 exception); 317 } catch (IllegalAccessException exception) { 318 } catch (InvocationTargetException exception) { 319 throw exception.getTargetException(); 320 } 321 } 322 323 private void invoke(String name, Object[] argv, 324 Class<?>... parameters) throws Throwable { 325 MethodUtils.invokeMethod(this, true, name, argv, parameters); 326 } 327 328 /** 329 * {@link ApplicationPath} type (interface) {@link Annotation} 330 * 331 * @param annotation The {@link ApplicationPath} 332 * {@link Annotation}. 333 * @param type The annotated {@link Class}. 334 * 335 * @throws Throwable If the {@link Annotation} cannot be 336 * configured. 337 */ 338 protected void type(ApplicationPath annotation, 339 Class<?> type) throws Throwable { 340 uri = uri.uri(annotation.value()); 341 } 342 343 /** 344 * {@link ConstrainedTo} type (interface) {@link Annotation} 345 * 346 * @param annotation The {@link ConstrainedTo} 347 * {@link Annotation}. 348 * @param type The annotated {@link Class}. 349 * 350 * @throws Throwable If the {@link Annotation} cannot be 351 * configured. 352 */ 353 protected void type(ConstrainedTo annotation, 354 Class<?> type) throws Throwable { 355 throw new UnsupportedOperationException(annotation.toString()); 356 } 357 358 /** 359 * {@link Consumes} type (interface) {@link Annotation} 360 * 361 * @param annotation The {@link Consumes} {@link Annotation}. 362 * @param type The annotated {@link Class}. 363 * 364 * @throws Throwable If the {@link Annotation} cannot be 365 * configured. 366 */ 367 protected void type(Consumes annotation, Class<?> type) throws Throwable { 368 throw new UnsupportedOperationException(annotation.toString()); 369 } 370 371 /** 372 * {@link Path} type (interface) {@link Annotation} 373 * 374 * @param annotation The {@link Path} {@link Annotation}. 375 * @param type The annotated {@link Class}. 376 * 377 * @throws Throwable If the {@link Annotation} cannot be 378 * configured. 379 */ 380 protected void type(Path annotation, Class<?> type) throws Throwable { 381 uri = uri.path(annotation.value()); 382 } 383 384 /** 385 * {@link Produces} type (interface) {@link Annotation} 386 * 387 * @param annotation The {@link Produces} {@link Annotation}. 388 * @param type The annotated {@link Class}. 389 * 390 * @throws Throwable If the {@link Annotation} cannot be 391 * configured. 392 */ 393 protected void type(Produces annotation, Class<?> type) throws Throwable { 394 throw new UnsupportedOperationException(annotation.toString()); 395 } 396 397 /** 398 * {@link BeanParam} method {@link Annotation} 399 * 400 * @param annotation The {@link BeanParam} 401 * {@link Annotation}. 402 * @param method The annotated {@link Method}. 403 * 404 * @throws Throwable If the {@link Annotation} cannot be 405 * configured. 406 */ 407 protected void method(BeanParam annotation, 408 Method method) throws Throwable { 409 throw new UnsupportedOperationException(annotation.toString()); 410 } 411 412 /** 413 * {@link Consumes} method {@link Annotation} 414 * 415 * @param annotation The {@link Consumes} {@link Annotation}. 416 * @param method The annotated {@link Method}. 417 * 418 * @throws Throwable If the {@link Annotation} cannot be 419 * configured. 420 */ 421 protected void method(Consumes annotation, 422 Method method) throws Throwable { 423 throw new UnsupportedOperationException(annotation.toString()); 424 } 425 426 /** 427 * {@link CookieParam} method {@link Annotation} 428 * 429 * @param annotation The {@link CookieParam} {@link Annotation}. 430 * @param method The annotated {@link Method}. 431 * 432 * @throws Throwable If the {@link Annotation} cannot be 433 * configured. 434 */ 435 protected void method(CookieParam annotation, 436 Method method) throws Throwable { 437 throw new UnsupportedOperationException(annotation.toString()); 438 } 439 440 /** 441 * {@link DELETE} method {@link Annotation} 442 * 443 * @param annotation The {@link DELETE} {@link Annotation}. 444 * @param method The annotated {@link Method}. 445 * 446 * @throws Throwable If the {@link Annotation} cannot be 447 * configured. 448 */ 449 protected void method(DELETE annotation, Method method) throws Throwable { 450 request = new HttpDelete(); 451 } 452 453 /** 454 * {@link FormParam} method {@link Annotation} 455 * 456 * @param annotation The {@link FormParam} {@link Annotation}. 457 * @param method The annotated {@link Method}. 458 * 459 * @throws Throwable If the {@link Annotation} cannot be 460 * configured. 461 */ 462 protected void method(FormParam annotation, 463 Method method) throws Throwable { 464 throw new UnsupportedOperationException(annotation.toString()); 465 } 466 467 /** 468 * {@link GET} method {@link Annotation} 469 * 470 * @param annotation The {@link GET} {@link Annotation}. 471 * @param method The annotated {@link Method}. 472 * 473 * @throws Throwable If the {@link Annotation} cannot be 474 * configured. 475 */ 476 protected void method(GET annotation, Method method) throws Throwable { 477 request = new HttpGet(); 478 } 479 480 /** 481 * {@link HEAD} method {@link Annotation} 482 * 483 * @param annotation The {@link HEAD} {@link Annotation}. 484 * @param method The annotated {@link Method}. 485 * 486 * @throws Throwable If the {@link Annotation} cannot be 487 * configured. 488 */ 489 protected void method(HEAD annotation, Method method) throws Throwable { 490 request = new HttpHead(); 491 } 492 493 /** 494 * {@link HeaderParam} method {@link Annotation} 495 * 496 * @param annotation The {@link HeaderParam} {@link Annotation}. 497 * @param method The annotated {@link Method}. 498 * 499 * @throws Throwable If the {@link Annotation} cannot be 500 * configured. 501 */ 502 protected void method(HeaderParam annotation, 503 Method method) throws Throwable { 504 throw new UnsupportedOperationException(annotation.toString()); 505 } 506 507 /** 508 * {@link MatrixParam} method {@link Annotation} 509 * 510 * @param annotation The {@link MatrixParam} {@link Annotation}. 511 * @param method The annotated {@link Method}. 512 * 513 * @throws Throwable If the {@link Annotation} cannot be 514 * configured. 515 */ 516 protected void method(MatrixParam annotation, 517 Method method) throws Throwable { 518 throw new UnsupportedOperationException(annotation.toString()); 519 } 520 521 /** 522 * {@link OPTIONS} method {@link Annotation} 523 * 524 * @param annotation The {@link OPTIONS} {@link Annotation}. 525 * @param method The annotated {@link Method}. 526 * 527 * @throws Throwable If the {@link Annotation} cannot be 528 * configured. 529 */ 530 protected void method(OPTIONS annotation, Method method) throws Throwable { 531 request = new HttpOptions(); 532 } 533 534 /** 535 * {@link PATCH} method {@link Annotation} 536 * 537 * @param annotation The {@link PATCH} {@link Annotation}. 538 * @param method The annotated {@link Method}. 539 * 540 * @throws Throwable If the {@link Annotation} cannot be 541 * configured. 542 */ 543 protected void method(PATCH annotation, Method method) throws Throwable { 544 request = new HttpPatch(); 545 } 546 547 /** 548 * {@link Path} method {@link Annotation} 549 * 550 * @param annotation The {@link Path} {@link Annotation}. 551 * @param method The annotated {@link Method}. 552 * 553 * @throws Throwable If the {@link Annotation} cannot be 554 * configured. 555 */ 556 protected void method(Path annotation, Method method) throws Throwable { 557 uri = uri.path(annotation.value()); 558 } 559 560 /** 561 * {@link PathParam} method {@link Annotation} 562 * 563 * @param annotation The {@link PathParam} {@link Annotation}. 564 * @param method The annotated {@link Method}. 565 * 566 * @throws Throwable If the {@link Annotation} cannot be 567 * configured. 568 */ 569 protected void method(PathParam annotation, 570 Method method) throws Throwable { 571 throw new UnsupportedOperationException(annotation.toString()); 572 } 573 574 /** 575 * {@link QueryParam} method {@link Annotation} 576 * 577 * @param annotation The {@link QueryParam} {@link Annotation}. 578 * @param method The annotated {@link Method}. 579 * 580 * @throws Throwable If the {@link Annotation} cannot be 581 * configured. 582 */ 583 protected void method(QueryParam annotation, 584 Method method) throws Throwable { 585 throw new UnsupportedOperationException(annotation.toString()); 586 } 587 588 /** 589 * {@link POST} method {@link Annotation} 590 * 591 * @param annotation The {@link POST} {@link Annotation}. 592 * @param method The annotated {@link Method}. 593 * 594 * @throws Throwable If the {@link Annotation} cannot be 595 * configured. 596 */ 597 protected void method(POST annotation, Method method) throws Throwable { 598 request = new HttpPost(); 599 } 600 601 /** 602 * {@link Produces} method {@link Annotation} 603 * 604 * @param annotation The {@link Produces} {@link Annotation}. 605 * @param method The annotated {@link Method}. 606 * 607 * @throws Throwable If the {@link Annotation} cannot be 608 * configured. 609 */ 610 protected void method(Produces annotation, 611 Method method) throws Throwable { 612 throw new UnsupportedOperationException(annotation.toString()); 613 } 614 615 /** 616 * {@link PUT} method {@link Annotation} 617 * 618 * @param annotation The {@link PUT} {@link Annotation}. 619 * @param method The annotated {@link Method}. 620 * 621 * @throws Throwable If the {@link Annotation} cannot be 622 * configured. 623 */ 624 protected void method(PUT annotation, Method method) throws Throwable { 625 request = new HttpPut(); 626 } 627 628 /** 629 * {@link BeanParam} method parameter {@link Annotation} 630 * 631 * @param annotation The {@link BeanParam} {@link Annotation}. 632 * @param parameter The {@link Method} {@link Parameter}. 633 * @param argument The {@link Object} representing the cookie 634 * parameter value. 635 * 636 * @throws Throwable If the {@link Annotation} cannot be 637 * configured. 638 */ 639 protected void parameter(BeanParam annotation, 640 Parameter parameter, 641 Object argument) throws Throwable { 642 if (argument != null) { 643 throw new UnsupportedOperationException(annotation.toString()); 644 } 645 } 646 647 /** 648 * {@link CookieParam} method parameter {@link Annotation} 649 * 650 * @param annotation The {@link CookieParam} {@link Annotation}. 651 * @param parameter The {@link Method} {@link Parameter}. 652 * @param argument The {@link Object} representing the cookie 653 * parameter value. 654 * 655 * @throws Throwable If the {@link Annotation} cannot be 656 * configured. 657 */ 658 protected void parameter(CookieParam annotation, 659 Parameter parameter, 660 Object argument) throws Throwable { 661 throw new UnsupportedOperationException(annotation.toString()); 662 } 663 664 /** 665 * {@link FormParam} method parameter {@link Annotation} 666 * 667 * @param annotation The {@link FormParam} {@link Annotation}. 668 * @param parameter The {@link Method} {@link Parameter}. 669 * @param argument The {@link Object} representing the form 670 * parameter value. 671 * 672 * @throws Throwable If the {@link Annotation} cannot be 673 * configured. 674 */ 675 protected void parameter(FormParam annotation, 676 Parameter parameter, 677 Object argument) throws Throwable { 678 String name = 679 isNotBlank(annotation.value()) 680 ? annotation.value() 681 : parameter.getName(); 682 683 if (argument != null) { 684 if (body == null) { 685 body = new Form(); 686 } 687 688 ((Form) body).add(name, String.valueOf(argument)); 689 } 690 } 691 692 /** 693 * {@link HeaderParam} method parameter {@link Annotation} 694 * 695 * @param annotation The {@link HeaderParam} {@link Annotation}. 696 * @param parameter The {@link Method} {@link Parameter}. 697 * @param argument The {@link Object} representing the header 698 * parameter value. 699 * 700 * @throws Throwable If the {@link Annotation} cannot be 701 * configured. 702 */ 703 protected void parameter(HeaderParam annotation, 704 Parameter parameter, 705 Object argument) throws Throwable { 706 String name = 707 isNotBlank(annotation.value()) 708 ? annotation.value() 709 : parameter.getName(); 710 711 if (argument != null) { 712 request.setHeader(name, String.valueOf(argument)); 713 } 714 } 715 716 /** 717 * {@link MatrixParam} method parameter {@link Annotation} 718 * 719 * @param annotation The {@link MatrixParam} {@link Annotation}. 720 * @param parameter The {@link Method} {@link Parameter}. 721 * @param argument The {@link Object} representing the matrix 722 * parameter value. 723 * 724 * @throws Throwable If the {@link Annotation} cannot be 725 * configured. 726 */ 727 protected void parameter(MatrixParam annotation, 728 Parameter parameter, 729 Object argument) throws Throwable { 730 String name = 731 isNotBlank(annotation.value()) 732 ? annotation.value() 733 : parameter.getName(); 734 735 uri = uri.replaceMatrixParam(name, argument); 736 } 737 738 /** 739 * {@link PathParam} method parameter {@link Annotation} 740 * 741 * @param annotation The {@link PathParam} {@link Annotation}. 742 * @param parameter The {@link Method} {@link Parameter}. 743 * @param argument The {@link Object} representing the path 744 * parameter value. 745 * 746 * @throws Throwable If the {@link Annotation} cannot be 747 * configured. 748 */ 749 protected void parameter(PathParam annotation, 750 Parameter parameter, 751 Object argument) throws Throwable { 752 String name = 753 isNotBlank(annotation.value()) 754 ? annotation.value() 755 : parameter.getName(); 756 757 if (argument != null) { 758 templateValues.put(name, String.valueOf(argument)); 759 } else { 760 templateValues.remove(name); 761 } 762 } 763 764 /** 765 * {@link QueryParam} method parameter {@link Annotation} 766 * 767 * @param annotation The {@link QueryParam} {@link Annotation}. 768 * @param parameter The {@link Method} {@link Parameter}. 769 * @param argument The {@link Object} representing the query 770 * parameter value. 771 * 772 * @throws Throwable If the {@link Annotation} cannot be 773 * configured. 774 */ 775 protected void parameter(QueryParam annotation, 776 Parameter parameter, 777 Object argument) throws Throwable { 778 String name = 779 isNotBlank(annotation.value()) 780 ? annotation.value() 781 : parameter.getName(); 782 783 uri = uri.replaceQueryParam(name, argument); 784 } 785 786 /** 787 * {@link HttpMessage} method parameter 788 * 789 * @param parameter The {@link Method} {@link Parameter}. 790 * @param argument The {@link HttpMessage}. 791 * 792 * @throws Throwable If the argument cannot be configured. 793 */ 794 protected void parameter(Parameter parameter, 795 HttpMessage argument) throws Throwable { 796 request = argument; 797 } 798 799 /** 800 * {@link HttpEntity} method parameter 801 * 802 * @param parameter The {@link Method} {@link Parameter}. 803 * @param argument The {@link HttpEntity}. 804 * 805 * @throws Throwable If the argument cannot be configured. 806 */ 807 protected void parameter(Parameter parameter, 808 HttpEntity argument) throws Throwable { 809 body = argument; 810 } 811 812 /** 813 * {@link URI} method parameter 814 * 815 * @param parameter The {@link Method} {@link Parameter}. 816 * @param argument The {@link URI}. 817 * 818 * @throws Throwable If the argument cannot be configured. 819 */ 820 protected void parameter(Parameter parameter, 821 URI argument) throws Throwable { 822 uri = uri.uri(argument); 823 } 824 825 /** 826 * {@link Object} method parameter 827 * 828 * @param parameter The {@link Method} {@link Parameter}. 829 * @param argument The {@link Object}. 830 * 831 * @throws Throwable If the argument cannot be configured. 832 */ 833 protected void parameter(Parameter parameter, 834 Object argument) throws Throwable { 835 body = argument; 836 } 837 838 private class Form extends ArrayList<NameValuePair> { 839 private static final long serialVersionUID = -738222384949508109L; 840 841 public Form() { super(); } 842 843 public boolean add(String name, String value) { 844 return add(new BasicNameValuePair(name, value)); 845 } 846 } 847 848 private abstract class HttpEntityImpl extends AbstractHttpEntity { 849 protected final Object object; 850 851 protected HttpEntityImpl(Object object) { 852 super(); 853 854 this.object = requireNonNull(object, "object"); 855 856 setChunked(false); 857 } 858 859 @Override 860 public boolean isRepeatable() { return true; } 861 862 @Override 863 public long getContentLength() { return -1; } 864 865 @Override 866 public InputStream getContent() throws IOException, 867 IllegalStateException { 868 ByteArrayDataSource ds = new ByteArrayDataSource(null, null); 869 870 try (OutputStream out = ds.getOutputStream()) { 871 writeTo(out); 872 } 873 874 return ds.getInputStream(); 875 } 876 877 @Override 878 public boolean isStreaming() { return false; } 879 } 880 881 private class JSONHttpEntity extends HttpEntityImpl { 882 public JSONHttpEntity(Object object) { 883 super(object); 884 885 setContentType(ContentType.APPLICATION_JSON 886 .withCharset(client.getCharset()) 887 .toString()); 888 } 889 890 @Override 891 public void writeTo(OutputStream out) throws IOException { 892 client.getObjectMapper().writeValue(out, object); 893 } 894 } 895}