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