001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest.client; 014 015import static org.apache.juneau.internal.StringUtils.*; 016import static org.apache.juneau.internal.ClassUtils.*; 017import static org.apache.juneau.httppart.HttpPartType.*; 018 019import java.io.*; 020import java.lang.reflect.*; 021import java.lang.reflect.Proxy; 022import java.net.*; 023import java.net.URI; 024import java.util.*; 025import java.util.concurrent.*; 026import java.util.regex.*; 027 028import org.apache.http.*; 029import org.apache.http.client.ClientProtocolException; 030import org.apache.http.client.methods.*; 031import org.apache.http.client.utils.*; 032import org.apache.http.entity.*; 033import org.apache.http.impl.client.*; 034import org.apache.juneau.*; 035import org.apache.juneau.annotation.*; 036import org.apache.juneau.httppart.*; 037import org.apache.juneau.httppart.bean.*; 038import org.apache.juneau.internal.*; 039import org.apache.juneau.json.*; 040import org.apache.juneau.oapi.*; 041import org.apache.juneau.parser.*; 042import org.apache.juneau.remote.*; 043import org.apache.juneau.rest.client.remote.*; 044import org.apache.juneau.serializer.*; 045import org.apache.juneau.urlencoding.*; 046import org.apache.juneau.utils.*; 047 048/** 049 * Utility class for interfacing with remote REST interfaces. 050 * 051 * <h5 class='topic'>Features</h5> 052 * <ul class='spaced-list'> 053 * <li> 054 * Convert POJOs directly to HTTP request message bodies using {@link Serializer} class. 055 * <li> 056 * Convert HTTP response message bodies directly to POJOs using {@link Parser} class. 057 * <li> 058 * Fluent interface. 059 * <li> 060 * Thread safe. 061 * <li> 062 * API for interacting with remote services. 063 * </ul> 064 * 065 * 066 * <ul class='seealso'> 067 * <li class='link'>{@doc juneau-rest-client} 068 * </ul> 069 */ 070@SuppressWarnings("rawtypes") 071@ConfigurableContext(nocache=true) 072public class RestClient extends BeanContext implements Closeable { 073 074 //------------------------------------------------------------------------------------------------------------------- 075 // Configurable properties 076 //------------------------------------------------------------------------------------------------------------------- 077 078 private static final String PREFIX = "RestClient."; 079 080 /** 081 * Configuration property: Debug. 082 * 083 * <h5 class='section'>Property:</h5> 084 * <ul> 085 * <li><b>Name:</b> <js>"RestClient.debug.b"</js> 086 * <li><b>Data type:</b> <c>Boolean</c> 087 * <li><b>Default:</b> <jk>false</jk> 088 * <li><b>Methods:</b> 089 * <ul> 090 * <li class='jm'>{@link RestClientBuilder#debug()} 091 * </ul> 092 * </ul> 093 * 094 * <h5 class='section'>Description:</h5> 095 * <p> 096 * Enable debug mode. 097 */ 098 public static final String RESTCLIENT_debug = PREFIX + "debug.b"; 099 100 /** 101 * Configuration property: Executor service. 102 * 103 * <h5 class='section'>Property:</h5> 104 * <ul> 105 * <li><b>Name:</b> <js>"RestClient.executorService.o"</js> 106 * <li><b>Data type:</b> <code>Class<? <jk>implements</jk> ExecutorService></code> or {@link ExecutorService}. 107 * <li><b>Default:</b> <jk>null</jk>. 108 * <li><b>Methods:</b> 109 * <ul> 110 * <li class='jm'>{@link RestClientBuilder#executorService(ExecutorService, boolean)} 111 * </ul> 112 * </ul> 113 * 114 * <h5 class='section'>Description:</h5> 115 * <p> 116 * Defines the executor service to use when calling future methods on the {@link RestCall} class. 117 * 118 * <p> 119 * This executor service is used to create {@link Future} objects on the following methods: 120 * <ul> 121 * <li>{@link RestCall#runFuture()} 122 * <li>{@link RestCall#getResponseFuture(Class)} 123 * <li>{@link RestCall#getResponseFuture(Type,Type...)} 124 * <li>{@link RestCall#getResponseAsString()} 125 * </ul> 126 * 127 * <p> 128 * The default executor service is a single-threaded {@link ThreadPoolExecutor} with a 30 second timeout 129 * and a queue size of 10. 130 */ 131 public static final String RESTCLIENT_executorService = PREFIX + "executorService.o"; 132 133 /** 134 * Configuration property: Shut down executor service on close. 135 * 136 * <h5 class='section'>Property:</h5> 137 * <ul> 138 * <li><b>Name:</b> <js>"RestClient.executorServiceShutdownOnClose.b"</js> 139 * <li><b>Data type:</b> <c>Boolean</c> 140 * <li><b>Default:</b> <jk>false</jk> 141 * <li><b>Methods:</b> 142 * <ul> 143 * <li class='jm'>{@link RestClientBuilder#executorService(ExecutorService, boolean)} 144 * </ul> 145 * </ul> 146 * 147 * <h5 class='section'>Description:</h5> 148 * <p> 149 * Call {@link ExecutorService#shutdown()} when {@link RestClient#close()} is called. 150 */ 151 public static final String RESTCLIENT_executorServiceShutdownOnClose = PREFIX + "executorServiceShutdownOnClose.b"; 152 153 /** 154 * Configuration property: Request headers. 155 * 156 * <h5 class='section'>Property:</h5> 157 * <ul> 158 * <li><b>Name:</b> <js>"RestClient.requestHeader.sms"</js> 159 * <li><b>Data type:</b> <c>Map<String,String></c> 160 * <li><b>Default:</b> empty map 161 * <li><b>Methods:</b> 162 * <ul> 163 * <li class='jm'>{@link RestClientBuilder#defaultHeaders(Collection)} 164 * <li class='jm'>{@link RestClientBuilder#header(String, Object)} 165 * </ul> 166 * </ul> 167 * 168 * <h5 class='section'>Description:</h5> 169 * <p> 170 * Headers to add to every request. 171 */ 172 public static final String RESTCLIENT_headers = PREFIX + "headers.sms"; 173 174 /** 175 * Configuration property: Call interceptors. 176 * 177 * <h5 class='section'>Property:</h5> 178 * <ul> 179 * <li><b>Name:</b> <js>"RestClient.interceptors.lo"</js> 180 * <li><b>Data type:</b> <code>List<Class<? <jk>implements</jk> RestCallInterceptor> | RestCallInterceptor></code> 181 * <li><b>Default:</b> empty list. 182 * <li><b>Methods:</b> 183 * <ul> 184 * <li class='jm'>{@link RestClientBuilder#interceptors(RestCallInterceptor...)} 185 * </ul> 186 * </ul> 187 * 188 * <h5 class='section'>Description:</h5> 189 * <p> 190 * Interceptors that get called immediately after a connection is made. 191 */ 192 public static final String RESTCLIENT_interceptors = PREFIX + "interceptors.lo"; 193 194 /** 195 * Add to the Call interceptors property. 196 */ 197 public static final String RESTCLIENT_interceptors_add = PREFIX + "interceptors.lo/add"; 198 199 /** 200 * Configuration property: Keep HttpClient open. 201 * 202 * <h5 class='section'>Property:</h5> 203 * <ul> 204 * <li><b>Name:</b> <js>"RestClient.keepHttpClientOpen.b"</js> 205 * <li><b>Data type:</b> <c>Boolean</c> 206 * <li><b>Default:</b> <jk>false</jk> 207 * <li><b>Methods:</b> 208 * <ul> 209 * <li class='jm'>{@link RestClientBuilder#keepHttpClientOpen(boolean)} 210 * </ul> 211 * </ul> 212 * 213 * <h5 class='section'>Description:</h5> 214 * <p> 215 * Don't close this client when the {@link RestClient#close()} method is called. 216 */ 217 public static final String RESTCLIENT_keepHttpClientOpen = PREFIX + "keepHttpClientOpen.b"; 218 219 /** 220 * Configuration property: Parser. 221 * 222 * <h5 class='section'>Property:</h5> 223 * <ul> 224 * <li><b>Name:</b> <js>"RestClient.parser.o"</js> 225 * <li><b>Data type:</b> <code>Class<? <jk>extends</jk> Parser></code> or {@link Parser}. 226 * <li><b>Default:</b> {@link JsonParser}; 227 * <li><b>Methods:</b> 228 * <ul> 229 * <li class='jm'>{@link RestClientBuilder#parser(Class)} 230 * <li class='jm'>{@link RestClientBuilder#parser(Parser)} 231 * </ul> 232 * </ul> 233 * 234 * <h5 class='section'>Description:</h5> 235 * <p> 236 * The parser to use for parsing POJOs in response bodies. 237 */ 238 public static final String RESTCLIENT_parser = PREFIX + "parser.o"; 239 240 /** 241 * Configuration property: Part parser. 242 * 243 * <h5 class='section'>Property:</h5> 244 * <ul> 245 * <li><b>Name:</b> <js>"RestClient.partParser.o"</js> 246 * <li><b>Data type:</b> <code>Class<? <jk>implements</jk> HttpPartParser></code> or {@link HttpPartParser}. 247 * <li><b>Default:</b> {@link OpenApiParser}; 248 * <li><b>Methods:</b> 249 * <ul> 250 * <li class='jm'>{@link RestClientBuilder#partParser(Class)} 251 * <li class='jm'>{@link RestClientBuilder#partParser(HttpPartParser)} 252 * </ul> 253 * </ul> 254 * 255 * <h5 class='section'>Description:</h5> 256 * <p> 257 * The parser to use for parsing POJOs from form data, query parameters, headers, and path variables. 258 */ 259 public static final String RESTCLIENT_partParser = PREFIX + "partParser.o"; 260 261 /** 262 * Configuration property: Part serializer. 263 * 264 * <h5 class='section'>Property:</h5> 265 * <ul> 266 * <li><b>Name:</b> <js>"RestClient.partSerializer.o"</js> 267 * <li><b>Data type:</b> <code>Class<? <jk>implements</jk> HttpPartSerializer></code> or {@link HttpPartSerializer}. 268 * <li><b>Default:</b> {@link OpenApiSerializer}; 269 * <li><b>Methods:</b> 270 * <ul> 271 * <li class='jm'>{@link RestClientBuilder#partSerializer(Class)} 272 * <li class='jm'>{@link RestClientBuilder#partSerializer(HttpPartSerializer)} 273 * </ul> 274 * </ul> 275 * 276 * <h5 class='section'>Description:</h5> 277 * <p> 278 * The serializer to use for serializing POJOs in form data, query parameters, headers, and path variables. 279 */ 280 public static final String RESTCLIENT_partSerializer = PREFIX + "partSerializer.o"; 281 282 /** 283 * Configuration property: Request query parameters. 284 * 285 * <h5 class='section'>Property:</h5> 286 * <ul> 287 * <li><b>Name:</b> <js>"RestClient.query.sms"</js> 288 * <li><b>Data type:</b> <c>Map<String,String></c> 289 * <li><b>Default:</b> empty map 290 * <li><b>Methods:</b> 291 * <ul> 292 * <li class='jm'>{@link RestClientBuilder#query(String, Object)} 293 * </ul> 294 * </ul> 295 * 296 * <h5 class='section'>Description:</h5> 297 * <p> 298 * Query parameters to add to every request. 299 */ 300 public static final String RESTCLIENT_query = PREFIX + "query.sms"; 301 302 /** 303 * Configuration property: Number of retries to attempt. 304 * 305 * <h5 class='section'>Property:</h5> 306 * <ul> 307 * <li><b>Name:</b> <js>"RestClient.retries.i"</js> 308 * <li><b>Data type:</b> <c>Integer</c> 309 * <li><b>Default:</b> <c>1</c> 310 * <li><b>Methods:</b> 311 * <ul> 312 * <li class='jm'>{@link RestClientBuilder#retryable(int, int, RetryOn)} 313 * </ul> 314 * </ul> 315 * 316 * <h5 class='section'>Description:</h5> 317 * <p> 318 * The number of retries to attempt when the connection cannot be made or a <c>>400</c> response is received. 319 */ 320 public static final String RESTCLIENT_retries = PREFIX + "retries.i"; 321 322 /** 323 * Configuration property: The time in milliseconds between retry attempts. 324 * 325 * <h5 class='section'>Property:</h5> 326 * <ul> 327 * <li><b>Name:</b> <js>"RestClient.retryInterval.i"</js> 328 * <li><b>Data type:</b> <c>Integer</c> 329 * <li><b>Default:</b> <c>-1</c> 330 * <li><b>Methods:</b> 331 * <ul> 332 * <li class='jm'>{@link RestClientBuilder#retryable(int, int, RetryOn)} 333 * </ul> 334 * </ul> 335 * 336 * <h5 class='section'>Description:</h5> 337 * <p> 338 * The time in milliseconds between retry attempts. 339 * <c>-1</c> means retry immediately. 340 */ 341 public static final String RESTCLIENT_retryInterval = PREFIX + "retryInterval.i"; 342 343 /** 344 * Configuration property: Retry-on determination object. 345 * 346 * <h5 class='section'>Property:</h5> 347 * <ul> 348 * <li><b>Name:</b> <js>"RestClient.retryOn.o"</js> 349 * <li><b>Data type:</b> <c>Class<? extends {@link RetryOn}</c> or {@link RetryOn} 350 * <li><b>Default:</b> {@link RetryOn#DEFAULT} 351 * <li><b>Methods:</b> 352 * <ul> 353 * <li class='jm'>{@link RestClientBuilder#retryable(int, int, RetryOn)} 354 * </ul> 355 * </ul> 356 * 357 * <h5 class='section'>Description:</h5> 358 * <p> 359 * Object used for determining whether a retry should be attempted. 360 */ 361 public static final String RESTCLIENT_retryOn = PREFIX + "retryOn.o"; 362 363 /** 364 * Configuration property: Root URI. 365 * 366 * <h5 class='section'>Property:</h5> 367 * <ul> 368 * <li><b>Name:</b> <js>"RestClient.rootUri.s"</js> 369 * <li><b>Data type:</b> <c>String</c> 370 * <li><b>Default:</b> <jk>false</jk> 371 * <li><b>Methods:</b> 372 * <ul> 373 * <li class='jm'>{@link RestClientBuilder#rootUrl(Object)} 374 * </ul> 375 * </ul> 376 * 377 * <h5 class='section'>Description:</h5> 378 * <p> 379 * When set, relative URL strings passed in through the various rest call methods (e.g. {@link RestClient#doGet(Object)} 380 * will be prefixed with the specified root. 381 * <br>This root URL is ignored on those methods if you pass in a {@link URL}, {@link URI}, or an absolute URL string. 382 * <br>Trailing slashes are trimmed. 383 */ 384 public static final String RESTCLIENT_rootUri = PREFIX + "rootUri.s"; 385 386 /** 387 * Configuration property: Serializer. 388 * 389 * <h5 class='section'>Property:</h5> 390 * <ul> 391 * <li><b>Name:</b> <js>"RestClient.serializer.o"</js> 392 * <li><b>Data type:</b> <code>Class<? <jk>extends</jk> Serializer></code> or {@link Serializer}. 393 * <li><b>Default:</b> {@link JsonSerializer}; 394 * <li><b>Methods:</b> 395 * <ul> 396 * <li class='jm'>{@link RestClientBuilder#serializer(Class)} 397 * <li class='jm'>{@link RestClientBuilder#serializer(Serializer)} 398 * </ul> 399 * </ul> 400 * 401 * <h5 class='section'>Description:</h5> 402 * <p> 403 * The serializer to use for serializing POJOs in request bodies. 404 */ 405 public static final String RESTCLIENT_serializer = PREFIX + "serializer.o"; 406 407 private static final Set<String> NO_BODY_METHODS = Collections.unmodifiableSet(ASet.<String>create("GET","HEAD","DELETE","CONNECT","OPTIONS","TRACE")); 408 409 private static final ConcurrentHashMap<Class,HttpPartSerializer> partSerializerCache = new ConcurrentHashMap<>(); 410 411 private final Map<String,String> headers, query; 412 private final HttpClientBuilder httpClientBuilder; 413 private final CloseableHttpClient httpClient; 414 private final boolean keepHttpClientOpen, debug; 415 private final UrlEncodingSerializer urlEncodingSerializer; // Used for form posts only. 416 private final HttpPartSerializer partSerializer; 417 private final HttpPartParser partParser; 418 private final String rootUrl; 419 private volatile boolean isClosed = false; 420 private final StackTraceElement[] creationStack; 421 private StackTraceElement[] closedStack; 422 423 // These are read directly by RestCall. 424 final Serializer serializer; 425 final Parser parser; 426 final RetryOn retryOn; 427 final int retries; 428 final long retryInterval; 429 final RestCallInterceptor[] interceptors; 430 431 // This is lazy-created. 432 private volatile ExecutorService executorService; 433 private final boolean executorServiceShutdownOnClose; 434 435 /** 436 * Instantiates a new clean-slate {@link RestClientBuilder} object. 437 * 438 * @return A new {@link RestClientBuilder} object. 439 */ 440 public static RestClientBuilder create() { 441 return new RestClientBuilder(PropertyStore.DEFAULT, null); 442 } 443 444 /** 445 * Instantiates a new {@link RestClientBuilder} object using the specified serializer and parser. 446 * 447 * <p> 448 * Shortcut for calling <code>RestClient.<jsm>create</jsm>().serializer(s).parser(p);</code> 449 * 450 * @param s The serializer to use for output. 451 * @param p The parser to use for input. 452 * @return A new {@link RestClientBuilder} object. 453 */ 454 public static RestClientBuilder create(Serializer s, Parser p) { 455 return create().serializer(s).parser(p); 456 } 457 458 /** 459 * Instantiates a new {@link RestClientBuilder} object using the specified serializer and parser. 460 * 461 * <p> 462 * Shortcut for calling <code>RestClient.<jsm>create</jsm>().serializer(s).parser(p);</code> 463 * 464 * @param s The serializer class to use for output. 465 * @param p The parser class to use for input. 466 * @return A new {@link RestClientBuilder} object. 467 */ 468 public static RestClientBuilder create(Class<? extends Serializer> s, Class<? extends Parser> p) { 469 return create().serializer(s).parser(p); 470 } 471 472 @Override /* Context */ 473 public RestClientBuilder builder() { 474 return new RestClientBuilder(getPropertyStore(), httpClientBuilder); 475 } 476 477 /** 478 * Constructor. 479 * 480 * @param ps 481 * Configuration properties for this client. 482 * <br>Can be <jk>null</jk>. 483 * @param httpClientBuilder 484 * The HTTP client builder to use to create the HTTP client. 485 * <br>Can be <jk>null</jk>. 486 * @param httpClient 487 * The HTTP client. 488 * <br>Must not be <jk>null</jk>. 489 */ 490 @SuppressWarnings("unchecked") 491 protected RestClient( 492 PropertyStore ps, 493 HttpClientBuilder httpClientBuilder, 494 CloseableHttpClient httpClient) { 495 super(ps); 496 if (ps == null) 497 ps = PropertyStore.DEFAULT; 498 this.httpClientBuilder = httpClientBuilder; 499 this.httpClient = httpClient; 500 this.keepHttpClientOpen = getBooleanProperty(RESTCLIENT_keepHttpClientOpen, false); 501 this.headers = getMapProperty(RESTCLIENT_headers, String.class); 502 this.query = getMapProperty(RESTCLIENT_query, String.class); 503 this.retries = getIntegerProperty(RESTCLIENT_retries, 1); 504 this.retryInterval = getIntegerProperty(RESTCLIENT_retryInterval, -1); 505 this.retryOn = getInstanceProperty(RESTCLIENT_retryOn, RetryOn.class, RetryOn.DEFAULT); 506 this.debug = getBooleanProperty(RESTCLIENT_debug, false); 507 this.executorServiceShutdownOnClose = getBooleanProperty(RESTCLIENT_executorServiceShutdownOnClose, false); 508 this.rootUrl = StringUtils.nullIfEmpty(getStringProperty(RESTCLIENT_rootUri, "").replaceAll("\\/$", "")); 509 510 Object o = getProperty(RESTCLIENT_serializer, Object.class, null); 511 if (o instanceof Serializer) { 512 this.serializer = ((Serializer)o).builder().apply(ps).build(); 513 } else if (o instanceof Class) { 514 this.serializer = ContextCache.INSTANCE.create((Class<? extends Serializer>)o, ps); 515 } else { 516 this.serializer = null; 517 } 518 519 o = getProperty(RESTCLIENT_parser, Object.class, null); 520 if (o instanceof Parser) { 521 this.parser = ((Parser)o).builder().apply(ps).build(); 522 } else if (o instanceof Class) { 523 this.parser = ContextCache.INSTANCE.create((Class<? extends Parser>)o, ps); 524 } else { 525 this.parser = null; 526 } 527 528 this.urlEncodingSerializer = new SerializerBuilder(ps).build(UrlEncodingSerializer.class); 529 this.partSerializer = getInstanceProperty(RESTCLIENT_partSerializer, HttpPartSerializer.class, OpenApiSerializer.class, ResourceResolver.FUZZY, ps); 530 this.partParser = getInstanceProperty(RESTCLIENT_partParser, HttpPartParser.class, OpenApiParser.class, ResourceResolver.FUZZY, ps); 531 this.executorService = getInstanceProperty(RESTCLIENT_executorService, ExecutorService.class, null); 532 533 RestCallInterceptor[] rci = getInstanceArrayProperty(RESTCLIENT_interceptors, RestCallInterceptor.class, new RestCallInterceptor[0]); 534 if (debug) 535 rci = ArrayUtils.append(rci, RestCallLogger.DEFAULT); 536 this.interceptors = rci; 537 538 if (Boolean.getBoolean("org.apache.juneau.rest.client.RestClient.trackLifecycle")) 539 creationStack = Thread.currentThread().getStackTrace(); 540 else 541 creationStack = null; 542 } 543 544 /** 545 * Returns <jk>true</jk> if specified http method has content. 546 * <p> 547 * By default, anything not in this list can have content: <c>GET, HEAD, DELETE, CONNECT, OPTIONS, TRACE</c>. 548 * 549 * @param httpMethod The HTTP method. Must be upper-case. 550 * @return <jk>true</jk> if specified http method has content. 551 */ 552 protected boolean hasContent(String httpMethod) { 553 return ! NO_BODY_METHODS.contains(httpMethod); 554 } 555 556 /** 557 * Calls {@link CloseableHttpClient#close()} on the underlying {@link CloseableHttpClient}. 558 * 559 * <p> 560 * It's good practice to call this method after the client is no longer used. 561 * 562 * @throws IOException Thrown by underlying stream. 563 */ 564 @Override 565 public void close() throws IOException { 566 isClosed = true; 567 if (httpClient != null && ! keepHttpClientOpen) 568 httpClient.close(); 569 if (executorService != null && executorServiceShutdownOnClose) 570 executorService.shutdown(); 571 if (creationStack != null) 572 closedStack = Thread.currentThread().getStackTrace(); 573 } 574 575 /** 576 * Same as {@link #close()}, but ignores any exceptions. 577 */ 578 public void closeQuietly() { 579 isClosed = true; 580 try { 581 if (httpClient != null && ! keepHttpClientOpen) 582 httpClient.close(); 583 if (executorService != null && executorServiceShutdownOnClose) 584 executorService.shutdown(); 585 } catch (Throwable t) {} 586 if (creationStack != null) 587 closedStack = Thread.currentThread().getStackTrace(); 588 } 589 590 /** 591 * Execute the specified request. 592 * 593 * <p> 594 * Subclasses can override this method to provide specialized handling. 595 * 596 * @param req The HTTP request. 597 * @return The HTTP response. 598 * @throws IOException Stream exception occurred. 599 * @throws ClientProtocolException ignals an error in the HTTP protocol. 600 */ 601 protected HttpResponse execute(HttpUriRequest req) throws ClientProtocolException, IOException { 602 return httpClient.execute(req); 603 } 604 605 /** 606 * Perform a <c>GET</c> request against the specified URL. 607 * 608 * @param url 609 * The URL of the remote REST resource. 610 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 611 * @return 612 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 613 * as a parsed object. 614 * @throws RestCallException If any authentication errors occurred. 615 */ 616 public RestCall doGet(Object url) throws RestCallException { 617 return doCall("GET", url, false); 618 } 619 620 /** 621 * Perform a <c>PUT</c> request against the specified URL. 622 * 623 * @param url 624 * The URL of the remote REST resource. 625 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 626 * @param o 627 * The object to serialize and transmit to the URL as the body of the request. 628 * Can be of the following types: 629 * <ul class='spaced-list'> 630 * <li> 631 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 632 * <li> 633 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 634 * <li> 635 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 636 * {@link RestClient}. 637 * <li> 638 * {@link HttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 639 * </ul> 640 * @return 641 * A {@link RestCall} object that can be further tailored before executing the request 642 * and getting the response as a parsed object. 643 * @throws RestCallException If any authentication errors occurred. 644 */ 645 public RestCall doPut(Object url, Object o) throws RestCallException { 646 return doCall("PUT", url, true).body(o); 647 } 648 649 /** 650 * Same as {@link #doPut(Object, Object)} but don't specify the input yet. 651 * 652 * <p> 653 * You must call either {@link RestCall#body(Object)} or {@link RestCall#formData(String, Object)} 654 * to set the contents on the result object. 655 * 656 * @param url 657 * The URL of the remote REST resource. 658 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 659 * @return 660 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 661 * as a parsed object. 662 * @throws RestCallException REST call failed. 663 */ 664 public RestCall doPut(Object url) throws RestCallException { 665 return doCall("PUT", url, true); 666 } 667 668 /** 669 * Perform a <c>POST</c> request against the specified URL. 670 * 671 * <ul class='notes'> 672 * <li>Use {@link #doFormPost(Object, Object)} for <c>application/x-www-form-urlencoded</c> form posts. 673 * </ul> 674 * 675 * @param url 676 * The URL of the remote REST resource. 677 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 678 * @param o 679 * The object to serialize and transmit to the URL as the body of the request. 680 * Can be of the following types: 681 * <ul class='spaced-list'> 682 * <li> 683 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 684 * <li> 685 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 686 * <li> 687 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the {@link RestClient}. 688 * <li> 689 * {@link HttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 690 * </ul> 691 * @return 692 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 693 * as a parsed object. 694 * @throws RestCallException If any authentication errors occurred. 695 */ 696 public RestCall doPost(Object url, Object o) throws RestCallException { 697 return doCall("POST", url, true).body(o); 698 } 699 700 /** 701 * Same as {@link #doPost(Object, Object)} but don't specify the input yet. 702 * 703 * <p> 704 * You must call either {@link RestCall#body(Object)} or {@link RestCall#formData(String, Object)} to set the 705 * contents on the result object. 706 * 707 * <ul class='notes'> 708 * <li>Use {@link #doFormPost(Object, Object)} for <c>application/x-www-form-urlencoded</c> form posts. 709 * </ul> 710 * 711 * @param url 712 * The URL of the remote REST resource. 713 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 714 * @return 715 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 716 * as a parsed object. 717 * @throws RestCallException REST call failed. 718 */ 719 public RestCall doPost(Object url) throws RestCallException { 720 return doCall("POST", url, true); 721 } 722 723 /** 724 * Perform a <c>DELETE</c> request against the specified URL. 725 * 726 * @param url 727 * The URL of the remote REST resource. 728 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 729 * @return 730 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 731 * as a parsed object. 732 * @throws RestCallException If any authentication errors occurred. 733 */ 734 public RestCall doDelete(Object url) throws RestCallException { 735 return doCall("DELETE", url, false); 736 } 737 738 /** 739 * Perform an <c>OPTIONS</c> request against the specified URL. 740 * 741 * @param url 742 * The URL of the remote REST resource. 743 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 744 * @return 745 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 746 * as a parsed object. 747 * @throws RestCallException If any authentication errors occurred. 748 */ 749 public RestCall doOptions(Object url) throws RestCallException { 750 return doCall("OPTIONS", url, true); 751 } 752 753 /** 754 * Perform a <c>POST</c> request with a content type of <c>application/x-www-form-urlencoded</c> 755 * against the specified URL. 756 * 757 * @param url 758 * The URL of the remote REST resource. 759 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 760 * @param o 761 * The object to serialize and transmit to the URL as the body of the request, serialized as a form post 762 * using the {@link UrlEncodingSerializer#DEFAULT} serializer. 763 * @return 764 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 765 * as a parsed object. 766 * @throws RestCallException If any authentication errors occurred. 767 */ 768 public RestCall doFormPost(Object url, Object o) throws RestCallException { 769 return doCall("POST", url, true) 770 .body(o instanceof HttpEntity ? o : new RestRequestEntity(o, urlEncodingSerializer, null)); 771 } 772 773 /** 774 * Perform a <c>PATCH</c> request against the specified URL. 775 * 776 * @param url 777 * The URL of the remote REST resource. 778 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 779 * @param o 780 * The object to serialize and transmit to the URL as the body of the request. 781 * Can be of the following types: 782 * <ul class='spaced-list'> 783 * <li> 784 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 785 * <li> 786 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 787 * <li> 788 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the {@link RestClient}. 789 * <li> 790 * {@link HttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 791 * </ul> 792 * @return 793 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 794 * as a parsed object. 795 * @throws RestCallException If any authentication errors occurred. 796 */ 797 public RestCall doPatch(Object url, Object o) throws RestCallException { 798 return doCall("PATCH", url, true).body(o); 799 } 800 801 /** 802 * Same as {@link #doPatch(Object, Object)} but don't specify the input yet. 803 * 804 * <p> 805 * You must call either {@link RestCall#body(Object)} or {@link RestCall#formData(String, Object)} to set the 806 * contents on the result object. 807 * 808 * <ul class='notes'> 809 * <li>Use {@link #doFormPost(Object, Object)} for <c>application/x-www-form-urlencoded</c> form posts. 810 * </ul> 811 * 812 * @param url 813 * The URL of the remote REST resource. 814 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 815 * @return 816 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 817 * as a parsed object. 818 * @throws RestCallException REST call failed. 819 */ 820 public RestCall doPatch(Object url) throws RestCallException { 821 return doCall("PATCH", url, true); 822 } 823 824 825 /** 826 * Performs a REST call where the entire call is specified in a simple string. 827 * 828 * <p> 829 * This method is useful for performing callbacks when the target of a callback is passed in 830 * on an initial request, for example to signal when a long-running process has completed. 831 * 832 * <p> 833 * The call string can be any of the following formats: 834 * <ul class='spaced-list'> 835 * <li> 836 * <js>"[method] [url]"</js> - e.g. <js>"GET http://localhost/callback"</js> 837 * <li> 838 * <js>"[method] [url] [payload]"</js> - e.g. <js>"POST http://localhost/callback some text payload"</js> 839 * <li> 840 * <js>"[method] [headers] [url] [payload]"</js> - e.g. <js>"POST {'Content-Type':'text/json'} http://localhost/callback {'some':'json'}"</js> 841 * </ul> 842 * <p> 843 * The payload will always be sent using a simple {@link StringEntity}. 844 * 845 * @param callString The call string. 846 * @return 847 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 848 * as a parsed object. 849 * @throws RestCallException REST call failed. 850 */ 851 public RestCall doCallback(String callString) throws RestCallException { 852 String s = callString; 853 try { 854 RestCall rc = null; 855 String method = null, uri = null, content = null; 856 ObjectMap h = null; 857 int i = s.indexOf(' '); 858 if (i != -1) { 859 method = s.substring(0, i).trim(); 860 s = s.substring(i).trim(); 861 if (s.length() > 0) { 862 if (s.charAt(0) == '{') { 863 i = s.indexOf('}'); 864 if (i != -1) { 865 String json = s.substring(0, i+1); 866 h = JsonParser.DEFAULT.parse(json, ObjectMap.class); 867 s = s.substring(i+1).trim(); 868 } 869 } 870 if (s.length() > 0) { 871 i = s.indexOf(' '); 872 if (i == -1) 873 uri = s; 874 else { 875 uri = s.substring(0, i).trim(); 876 s = s.substring(i).trim(); 877 if (s.length() > 0) 878 content = s; 879 } 880 } 881 } 882 } 883 if (method != null && uri != null) { 884 rc = doCall(method, uri, content != null); 885 if (content != null) 886 rc.body(new StringEntity(content)); 887 if (h != null) 888 for (Map.Entry<String,Object> e : h.entrySet()) 889 rc.header(e.getKey(), e.getValue()); 890 return rc; 891 } 892 } catch (Exception e) { 893 throw new RestCallException(e); 894 } 895 throw new RestCallException("Invalid format for call string."); 896 } 897 898 /** 899 * Perform a generic REST call. 900 * 901 * @param method The HTTP method. 902 * @param url 903 * The URL of the remote REST resource. 904 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 905 * @param content 906 * The HTTP body content. 907 * Can be of the following types: 908 * <ul class='spaced-list'> 909 * <li> 910 * {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource. 911 * <li> 912 * {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource. 913 * <li> 914 * {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the 915 * {@link RestClient}. 916 * <li> 917 * {@link HttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient. 918 * <li> 919 * {@link NameValuePairs} - Converted to a URL-encoded FORM post. 920 * </ul> 921 * This parameter is IGNORED if {@link HttpMethod#hasContent()} is <jk>false</jk>. 922 * @return 923 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 924 * as a parsed object. 925 * @throws RestCallException If any authentication errors occurred. 926 */ 927 public RestCall doCall(HttpMethod method, Object url, Object content) throws RestCallException { 928 RestCall rc = doCall(method.name(), url, method.hasContent()); 929 if (method.hasContent()) 930 rc.body(content); 931 return rc; 932 } 933 934 /** 935 * Perform a generic REST call. 936 * 937 * @param method The method name (e.g. <js>"GET"</js>, <js>"OPTIONS"</js>). 938 * @param url 939 * The URL of the remote REST resource. 940 * Can be any of the following: {@link String}, {@link URI}, {@link URL}. 941 * @param hasContent Boolean flag indicating if the specified request has content associated with it. 942 * @return 943 * A {@link RestCall} object that can be further tailored before executing the request and getting the response 944 * as a parsed object. 945 * @throws RestCallException If any authentication errors occurred. 946 */ 947 public RestCall doCall(String method, Object url, boolean hasContent) throws RestCallException { 948 if (isClosed) { 949 Exception e2 = null; 950 if (closedStack != null) { 951 e2 = new Exception("Creation stack:"); 952 e2.setStackTrace(closedStack); 953 throw new RestCallException(e2, "RestClient.close() has already been called. This client cannot be reused."); 954 } 955 throw new RestCallException("RestClient.close() has already been called. This client cannot be reused. Closed location stack trace can be displayed by setting the system property 'org.apache.juneau.rest.client.RestClient.trackCreation' to true."); 956 } 957 958 HttpRequestBase req = null; 959 RestCall restCall = null; 960 final String methodUC = method.toUpperCase(Locale.ENGLISH); 961 try { 962 if (hasContent) { 963 req = new HttpEntityEnclosingRequestBase() { 964 @Override /* HttpRequest */ 965 public String getMethod() { 966 return methodUC; 967 } 968 }; 969 restCall = new RestCall(this, req, toURI(url)); 970 } else { 971 req = new HttpRequestBase() { 972 @Override /* HttpRequest */ 973 public String getMethod() { 974 return methodUC; 975 } 976 }; 977 restCall = new RestCall(this, req, toURI(url)); 978 } 979 } catch (URISyntaxException e1) { 980 throw new RestCallException(e1); 981 } 982 983 for (Map.Entry<String,String> e : query.entrySet()) 984 restCall.query(e.getKey(), e.getValue()); 985 986 for (Map.Entry<String,String> e : headers.entrySet()) 987 restCall.header(e.getKey(), e.getValue()); 988 989 if (parser != null && ! req.containsHeader("Accept")) 990 req.setHeader("Accept", parser.getPrimaryMediaType().toString()); 991 992 return restCall; 993 } 994 995 /** 996 * Create a new proxy interface against a 3rd-party REST interface. 997 * 998 * <p> 999 * The URL to the REST interface is based on the following values: 1000 * <ul> 1001 * <li>The {@link RemoteResource#path() @RemoteResource(path)} annotation on the interface (<c>remote-path</c>). 1002 * <li>The {@link RestClientBuilder#rootUrl(Object) rootUrl} on the client (<c>root-url</c>). 1003 * <li>The fully-qualified class name of the interface (<c>class-name</c>). 1004 * </ul> 1005 * 1006 * <p> 1007 * The URL calculation is as follows: 1008 * <ul> 1009 * <li><c>remote-path</c> - If remote path is absolute. 1010 * <li><c>root-url/remote-path</c> - If remote path is relative and root-url has been specified. 1011 * <li><c>root-url/class-name</c> - If remote path is not specified. 1012 * </ul> 1013 * 1014 * <p> 1015 * If the information is not available to resolve to an absolute URL, a {@link RemoteMetadataException} is thrown. 1016 * 1017 * <p> 1018 * Examples: 1019 * <p class='bcode w800'> 1020 * <jk>package</jk> org.apache.foo; 1021 * 1022 * <ja>@RemoteResource</ja>(path=<js>"http://hostname/resturl/myinterface1"</js>) 1023 * <jk>public interface</jk> MyInterface1 { ... } 1024 * 1025 * <ja>@RemoteResource</ja>(path=<js>"/myinterface2"</js>) 1026 * <jk>public interface</jk> MyInterface2 { ... } 1027 * 1028 * <jk>public interface</jk> MyInterface3 { ... } 1029 * 1030 * <jc>// Resolves to "http://localhost/resturl/myinterface1"</jc> 1031 * MyInterface1 i1 = RestClient 1032 * .<jsm>create</jsm>() 1033 * .build() 1034 * .getRemoteResource(MyInterface1.<jk>class</jk>); 1035 * 1036 * <jc>// Resolves to "http://hostname/resturl/myinterface2"</jc> 1037 * MyInterface2 i2 = RestClient 1038 * .<jsm>create</jsm>() 1039 * .rootUrl(<js>"http://hostname/resturl"</js>) 1040 * .build() 1041 * .getRemoteResource(MyInterface2.<jk>class</jk>); 1042 * 1043 * <jc>// Resolves to "http://hostname/resturl/org.apache.foo.MyInterface3"</jc> 1044 * MyInterface3 i3 = RestClient 1045 * .<jsm>create</jsm>() 1046 * .rootUrl(<js>"http://hostname/resturl"</js>) 1047 * .build() 1048 * .getRemoteResource(MyInterface3.<jk>class</jk>); 1049 * </p> 1050 * 1051 * <ul class='notes'> 1052 * <li> 1053 * If you plan on using your proxy in a multi-threaded environment, you'll want to use an underlying 1054 * pooling client connection manager. 1055 * The easiest way to do this is to use the {@link RestClientBuilder#pooled()} method. 1056 * If you don't do this, you may end up seeing "Connection still allocated" exceptions. 1057 * </ul> 1058 * 1059 * @param interfaceClass The interface to create a proxy for. 1060 * @return The new proxy interface. 1061 * @throws RemoteMetadataException If the REST URI cannot be determined based on the information given. 1062 */ 1063 public <T> T getRemoteResource(final Class<T> interfaceClass) { 1064 return getRemoteResource(interfaceClass, null); 1065 } 1066 1067 /** 1068 * Same as {@link #getRemoteResource(Class)} except explicitly specifies the URL of the REST interface. 1069 * 1070 * @param interfaceClass The interface to create a proxy for. 1071 * @param restUrl The URL of the REST interface. 1072 * @return The new proxy interface. 1073 */ 1074 public <T> T getRemoteResource(final Class<T> interfaceClass, final Object restUrl) { 1075 return getRemoteResource(interfaceClass, restUrl, serializer, parser); 1076 } 1077 1078 /** 1079 * Same as {@link #getRemoteResource(Class, Object)} but allows you to override the serializer and parser used. 1080 * 1081 * @param interfaceClass The interface to create a proxy for. 1082 * @param restUrl The URL of the REST interface. 1083 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 1084 * @param parser The parser used to parse POJOs from the body of the HTTP response. 1085 * @return The new proxy interface. 1086 */ 1087 @SuppressWarnings({ "unchecked" }) 1088 public <T> T getRemoteResource(final Class<T> interfaceClass, Object restUrl, final Serializer serializer, final Parser parser) { 1089 1090 if (restUrl == null) 1091 restUrl = rootUrl; 1092 1093 final String restUrl2 = trimSlashes(emptyIfNull(restUrl)); 1094 1095 try { 1096 return (T)Proxy.newProxyInstance( 1097 interfaceClass.getClassLoader(), 1098 new Class[] { interfaceClass }, 1099 new InvocationHandler() { 1100 1101 final RemoteResourceMeta rm = new RemoteResourceMeta(interfaceClass); 1102 1103 @Override /* InvocationHandler */ 1104 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1105 RemoteMethodMeta rmm = rm.getMethodMeta(method); 1106 1107 if (rmm == null) 1108 throw new RuntimeException("Method is not exposed as a remote method."); 1109 1110 String url = rmm.getFullPath(); 1111 if (url.indexOf("://") == -1) 1112 url = restUrl2 + '/' + url; 1113 if (url.indexOf("://") == -1) 1114 throw new RemoteMetadataException(interfaceClass, "Root URI has not been specified. Cannot construct absolute path to remote resource."); 1115 1116 String httpMethod = rmm.getHttpMethod(); 1117 HttpPartSerializer s = getPartSerializer(); 1118 1119 try (RestCall rc = doCall(httpMethod, url, hasContent(httpMethod))) { 1120 1121 rc.serializer(serializer).parser(parser); 1122 1123 for (RemoteMethodArg a : rmm.getPathArgs()) 1124 rc.path(a.getName(), args[a.getIndex()], a.getSerializer(s), a.getSchema()); 1125 1126 for (RemoteMethodArg a : rmm.getQueryArgs()) 1127 rc.query(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(s), a.getSchema()); 1128 1129 for (RemoteMethodArg a : rmm.getFormDataArgs()) 1130 rc.formData(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(s), a.getSchema()); 1131 1132 for (RemoteMethodArg a : rmm.getHeaderArgs()) 1133 rc.header(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(s), a.getSchema()); 1134 1135 RemoteMethodArg ba = rmm.getBodyArg(); 1136 if (ba != null) 1137 rc.requestBodySchema(ba.getSchema()).body(args[ba.getIndex()]); 1138 1139 if (rmm.getRequestArgs().length > 0) { 1140 for (RemoteMethodBeanArg rmba : rmm.getRequestArgs()) { 1141 RequestBeanMeta rbm = rmba.getMeta(); 1142 Object bean = args[rmba.getIndex()]; 1143 if (bean != null) { 1144 for (RequestBeanPropertyMeta p : rbm.getProperties()) { 1145 Object val = p.getGetter().invoke(bean); 1146 HttpPartType pt = p.getPartType(); 1147 HttpPartSerializer ps = p.getSerializer(s); 1148 String pn = p.getPartName(); 1149 HttpPartSchema schema = p.getSchema(); 1150 boolean sie = schema.isSkipIfEmpty(); 1151 if (pt == PATH) 1152 rc.path(pn, val, p.getSerializer(s), schema); 1153 else if (val != null) { 1154 if (pt == QUERY) 1155 rc.query(pn, val, sie, ps, schema); 1156 else if (pt == FORMDATA) 1157 rc.formData(pn, val, sie, ps, schema); 1158 else if (pt == HEADER) 1159 rc.header(pn, val, sie, ps, schema); 1160 else if (pt == HttpPartType.BODY) 1161 rc.requestBodySchema(schema).body(val); 1162 } 1163 } 1164 } 1165 } 1166 } 1167 1168 if (rmm.getOtherArgs().length > 0) { 1169 Object[] otherArgs = new Object[rmm.getOtherArgs().length]; 1170 int i = 0; 1171 for (RemoteMethodArg a : rmm.getOtherArgs()) 1172 otherArgs[i++] = args[a.getIndex()]; 1173 rc.body(otherArgs); 1174 } 1175 1176 RemoteMethodReturn rmr = rmm.getReturns(); 1177 if (rmr.getReturnValue() == RemoteReturn.NONE) { 1178 rc.run(); 1179 return null; 1180 } else if (rmr.getReturnValue() == RemoteReturn.STATUS) { 1181 rc.ignoreErrors(); 1182 int returnCode = rc.run(); 1183 Class<?> rt = method.getReturnType(); 1184 if (rt == Integer.class || rt == int.class) 1185 return returnCode; 1186 if (rt == Boolean.class || rt == boolean.class) 1187 return returnCode < 400; 1188 throw new RestCallException("Invalid return type on method annotated with @RemoteMethod(returns=HTTP_STATUS). Only integer and booleans types are valid."); 1189 } else if (rmr.getReturnValue() == RemoteReturn.BEAN) { 1190 return rc.getResponse(rmr.getResponseBeanMeta()); 1191 } else { 1192 Object v = rc.getResponseBody(rmr.getReturnType()); 1193 if (v == null && method.getReturnType().isPrimitive()) 1194 v = getClassInfo(method.getReturnType()).getPrimitiveDefault(); 1195 return v; 1196 } 1197 1198 } catch (RestCallException e) { 1199 // Try to throw original exception if possible. 1200 e.throwServerException(interfaceClass.getClassLoader(), rmm.getExceptions()); 1201 throw new RuntimeException(e); 1202 } catch (Exception e) { 1203 throw new RuntimeException(e); 1204 } 1205 } 1206 }); 1207 } catch (Exception e) { 1208 throw new RuntimeException(e); 1209 } 1210 } 1211 1212 /** 1213 * Create a new Remote Interface against a {@link RemoteInterface @RemoteInterface}-annotated class. 1214 * 1215 * <p> 1216 * Remote interfaces are interfaces exposed on the server side using either the <c>RrpcServlet</c> 1217 * or <c>RRPC</c> REST methods. 1218 * 1219 * <p> 1220 * The URL to the REST interface is based on the following values: 1221 * <ul> 1222 * <li>The {@link RemoteResource#path() @RemoteResource(path)} annotation on the interface (<c>remote-path</c>). 1223 * <li>The {@link RestClientBuilder#rootUrl(Object) rootUrl} on the client (<c>root-url</c>). 1224 * <li>The fully-qualified class name of the interface (<c>class-name</c>). 1225 * </ul> 1226 * 1227 * <p> 1228 * The URL calculation is as follows: 1229 * <ul> 1230 * <li><c>remote-path</c> - If remote path is absolute. 1231 * <li><c>root-url/remote-path</c> - If remote path is relative and root-url has been specified. 1232 * <li><c>root-url/class-name</c> - If remote path is not specified. 1233 * </ul> 1234 * 1235 * <p> 1236 * If the information is not available to resolve to an absolute URL, a {@link RemoteMetadataException} is thrown. 1237 * 1238 * <ul class='notes'> 1239 * <li> 1240 * If you plan on using your proxy in a multi-threaded environment, you'll want to use an underlying 1241 * pooling client connection manager. 1242 * The easiest way to do this is to use the {@link RestClientBuilder#pooled()} method. 1243 * If you don't do this, you may end up seeing "Connection still allocated" exceptions. 1244 * </ul> 1245 * 1246 * @param interfaceClass The interface to create a proxy for. 1247 * @return The new proxy interface. 1248 * @throws RemoteMetadataException If the REST URI cannot be determined based on the information given. 1249 */ 1250 public <T> T getRrpcInterface(final Class<T> interfaceClass) { 1251 return getRrpcInterface(interfaceClass, null); 1252 } 1253 1254 /** 1255 * Same as {@link #getRrpcInterface(Class)} except explicitly specifies the URL of the REST interface. 1256 * 1257 * @param interfaceClass The interface to create a proxy for. 1258 * @param restUrl The URL of the REST interface. 1259 * @return The new proxy interface. 1260 */ 1261 public <T> T getRrpcInterface(final Class<T> interfaceClass, final Object restUrl) { 1262 return getRrpcInterface(interfaceClass, restUrl, serializer, parser); 1263 } 1264 1265 /** 1266 * Same as {@link #getRrpcInterface(Class, Object)} but allows you to override the serializer and parser used. 1267 * 1268 * @param interfaceClass The interface to create a proxy for. 1269 * @param restUrl The URL of the REST interface. 1270 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request. 1271 * @param parser The parser used to parse POJOs from the body of the HTTP response. 1272 * @return The new proxy interface. 1273 */ 1274 @SuppressWarnings({ "unchecked" }) 1275 public <T> T getRrpcInterface(final Class<T> interfaceClass, Object restUrl, final Serializer serializer, final Parser parser) { 1276 1277 if (restUrl == null) { 1278 RemoteInterfaceMeta rm = new RemoteInterfaceMeta(interfaceClass, stringify(restUrl)); 1279 String path = rm.getPath(); 1280 if (path.indexOf("://") == -1) { 1281 if (rootUrl == null) 1282 throw new RemoteMetadataException(interfaceClass, "Root URI has not been specified. Cannot construct absolute path to remote interface."); 1283 path = trimSlashes(rootUrl) + '/' + path; 1284 } 1285 restUrl = path; 1286 } 1287 1288 final String restUrl2 = stringify(restUrl); 1289 1290 try { 1291 return (T)Proxy.newProxyInstance( 1292 interfaceClass.getClassLoader(), 1293 new Class[] { interfaceClass }, 1294 new InvocationHandler() { 1295 1296 final RemoteInterfaceMeta rm = new RemoteInterfaceMeta(interfaceClass, restUrl2); 1297 1298 @Override /* InvocationHandler */ 1299 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 1300 RemoteInterfaceMethod rim = rm.getMethodMeta(method); 1301 1302 if (rim == null) 1303 throw new RuntimeException("Method is not exposed as a remote method."); 1304 1305 String url = rim.getUrl(); 1306 1307 try (RestCall rc = doCall("POST", url, true)) { 1308 1309 rc.serializer(serializer).parser(parser).body(args); 1310 1311 Object v = rc.getResponse(method.getGenericReturnType()); 1312 if (v == null && method.getReturnType().isPrimitive()) 1313 v = getClassInfo(method.getReturnType()).getPrimitiveDefault(); 1314 return v; 1315 1316 } catch (RestCallException e) { 1317 // Try to throw original exception if possible. 1318 e.throwServerException(interfaceClass.getClassLoader(), method.getExceptionTypes()); 1319 throw new RuntimeException(e); 1320 } catch (Exception e) { 1321 throw new RuntimeException(e); 1322 } 1323 } 1324 }); 1325 } catch (Exception e) { 1326 throw new RuntimeException(e); 1327 } 1328 } 1329 1330 static final String getName(String name1, String name2, BeanPropertyMeta pMeta) { 1331 String n = name1.isEmpty() ? name2 : name1; 1332 ClassMeta<?> cm = pMeta.getClassMeta(); 1333 if (n.isEmpty() && (cm.isMapOrBean() || cm.isReader() || cm.isInstanceOf(NameValuePairs.class))) 1334 n = "*"; 1335 if (n.isEmpty()) 1336 n = pMeta.getName(); 1337 return n; 1338 } 1339 1340 final HttpPartSerializer getPartSerializer(Class c, HttpPartSerializer c2) { 1341 if (c2 != null) 1342 return c2; 1343 if (c == HttpPartSerializer.Null.class) 1344 return null; 1345 HttpPartSerializer pf = partSerializerCache.get(c); 1346 if (pf == null) { 1347 partSerializerCache.putIfAbsent(c, castOrCreate(HttpPartSerializer.class, c, true, getPropertyStore())); 1348 pf = partSerializerCache.get(c); 1349 } 1350 return pf; 1351 } 1352 1353 private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*"); 1354 1355 HttpPartSerializer getPartSerializer() { 1356 return partSerializer; 1357 } 1358 1359 HttpPartParser getPartParser() { 1360 return partParser; 1361 } 1362 1363 URI toURI(Object url) throws URISyntaxException { 1364 if (url instanceof URI) 1365 return (URI)url; 1366 if (url instanceof URL) 1367 ((URL)url).toURI(); 1368 if (url instanceof URIBuilder) 1369 return ((URIBuilder)url).build(); 1370 String s = url == null ? "" : url.toString(); 1371 if (rootUrl != null && ! absUrlPattern.matcher(s).matches()) { 1372 if (s.isEmpty()) 1373 s = rootUrl; 1374 else { 1375 StringBuilder sb = new StringBuilder(rootUrl); 1376 if (! s.startsWith("/")) 1377 sb.append('/'); 1378 sb.append(s); 1379 s = sb.toString(); 1380 } 1381 } 1382 if (s.indexOf('{') != -1) 1383 s = s.replace("{", "%7B").replace("}", "%7D"); 1384 return new URI(s); 1385 } 1386 1387 ExecutorService getExecutorService(boolean create) { 1388 if (executorService != null || ! create) 1389 return executorService; 1390 synchronized(this) { 1391 if (executorService == null) 1392 executorService = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10)); 1393 return executorService; 1394 } 1395 } 1396 1397 @Override 1398 protected void finalize() throws Throwable { 1399 if (! isClosed && ! keepHttpClientOpen) { 1400 System.err.println("WARNING: RestClient garbage collected before it was finalized."); // NOT DEBUG 1401 if (creationStack != null) { 1402 System.err.println("Creation Stack:"); // NOT DEBUG 1403 for (StackTraceElement e : creationStack) 1404 System.err.println(e); // NOT DEBUG 1405 } else { 1406 System.err.println("Creation stack traces can be displayed by setting the system property 'org.apache.juneau.rest.client.RestClient.trackLifecycle' to true."); // NOT DEBUG 1407 } 1408 } 1409 } 1410 1411 //----------------------------------------------------------------------------------------------------------------- 1412 // Other methods. 1413 //----------------------------------------------------------------------------------------------------------------- 1414 1415 @Override /* Context */ 1416 public ObjectMap toMap() { 1417 return super.toMap() 1418 .append("RestClient", new DefaultFilteringObjectMap() 1419 .append("debug", debug) 1420 .append("executorService", executorService) 1421 .append("executorServiceShutdownOnClose", executorServiceShutdownOnClose) 1422 .append("headers", headers) 1423 .append("interceptors", interceptors) 1424 .append("keepHttpClientOpen", keepHttpClientOpen) 1425 .append("parser", parser) 1426 .append("partParser", partParser) 1427 .append("partSerializer", partSerializer) 1428 .append("query", query) 1429 .append("retries", retries) 1430 .append("retryInterval", retryInterval) 1431 .append("retryOn", retryOn) 1432 .append("rootUri", rootUrl) 1433 .append("serializer", serializer) 1434 ); 1435 } 1436}