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