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