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