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