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