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