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