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