001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.mock.classic;
018
019import static java.util.Collections.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.commons.utils.StringUtils.*;
022import static org.apache.juneau.rest.server.util.RestUtils.*;
023
024import java.io.*;
025import java.net.*;
026import java.util.*;
027import java.util.concurrent.*;
028import java.util.logging.*;
029import java.util.zip.*;
030
031import org.apache.http.*;
032import org.apache.http.client.methods.*;
033import org.apache.http.conn.*;
034import org.apache.http.entity.*;
035import org.apache.http.message.*;
036import org.apache.juneau.commons.inject.*;
037import org.apache.juneau.http.classic.header.ContentType;
038import org.apache.juneau.http.remote.*;
039import org.apache.juneau.marshall.*;
040import org.apache.juneau.marshall.parser.*;
041import org.apache.juneau.rest.client.classic.*;
042import org.apache.juneau.rest.client.classic.RestRequest;
043import org.apache.juneau.rest.mock.*;
044import org.apache.juneau.rest.server.*;
045import org.apache.juneau.rest.server.logging.*;
046
047import jakarta.servlet.http.*;
048
049/**
050 * Mocked {@link RestClient}.
051 *
052 * <p>
053 *    This class is used for performing serverless unit testing of {@link Rest @Rest}-annotated and {@link Remote @Remote}-annotated classes.
054 *
055 * <p>
056 *    The class itself extends from {@link RestClient} providing it with the rich feature set of that API and combines
057 *    it with the Apache HttpClient {@link HttpClientConnection} interface for processing requests.
058 *  The class converts {@link HttpRequest} objects to instances of {@link MockServletRequest} and {@link MockServletResponse} which are passed directly
059 *  to the call handler on the resource class {@link RestContext#execute(Object,HttpServletRequest,HttpServletResponse)}.
060 *  In effect, you're fully testing your REST API as if it were running in a live servlet container, yet not
061 *  actually having to run in a servlet container.
062 *  All aspects of the client and server side code are tested, yet no servlet container is required.  The actual
063 *  over-the-wire transmission is the only aspect being bypassed.
064 *
065 * <p>
066 * The following shows a simple example of invoking a PUT method on a simple REST interface and asserting the correct status code and response body:
067 *
068 * <h5 class='figure'>Example:</h5>
069 * <p class='bjava'>
070 *    <jk>public class</jk> MockTest {
071 *
072 *       <jc>// A simple bean with one field.</jc>
073 *       <jk>public static class</jk> MyBean {
074 *          <jk>public int</jk> <jf>foo</jf> = 1;
075 *       }
076 *
077 *       <jc>// Our REST resource to test.</jc>
078 *       <jc>// Simply echos the response.</jc>
079 *       <ja>@Rest</ja>(
080 *          serializers=Json5Serializer.<jk>class</jk>,
081 *          parsers=JsonParser.<jk>class</jk>
082 *       )
083 *       <jk>public static class</jk> EchoRest {
084 *
085 *          <ja>@RestPut</ja>(
086 *             path=<js>"/echo"</js>
087 *          )
088 *          <jk>public</jk> MyBean echo(<ja>@Content</ja> MyBean <jv>bean</jv>) {
089 *             <jk>return</jk> <jv>bean</jv>;
090 *          }
091 *       }
092 *
093 *       <jc>// Our JUnit test.</jc>
094 *       <ja>@Test</ja>
095 *       <jk>public void</jk> testEcho() <jk>throws</jk> Exception {
096 *
097 *          MyBean <jv>myBean</jv> = <jk>new</jk> MyBean();
098 *
099 *          <jc>// Do a round-trip on the bean through the REST interface</jc>
100 *          <jv>myBean</jv> = MockRestClient
101 *             .<jsm>create</jsm>(EchoRest.<jk>class</jk>)
102 *             .json5()
103 *             .build()
104 *             .put(<js>"/echo"</js>, <jv>myBean</jv>)
105 *             .run()
106 *             .assertStatus().is(200)
107 *             .assertContent().is(<js>"{foo:1}"</js>)
108 *             .getContent().as(MyBean.<jk>class</jk>);
109 *
110 *          <jsm>assertEquals</jsm>(1, <jv>myBean</jv>.<jf>foo</jf>);
111 *       }
112 *    }
113 * </p>
114 * <p>
115 *    Breaking apart the fluent method call above will help you understand how this works.
116 *
117 * <p class='bjava'>
118 *    <ja>@Test</ja>
119 *    <jk>public void</jk> testEcho() <jk>throws</jk> Exception {
120 *
121 *       <jc>// Instantiate our mock client.</jc>
122 *       MockRestClient <jv>client</jv> = MockRestClient
123 *          .<jsm>create</jsm>(EchoRest.<jk>class</jk>)
124 *          .json5()
125 *          .build();
126 *
127 *       <jc>// Create a request.</jc>
128 *       RestRequest <jv>req</jv> = <jv>client</jv>.put(<js>"/echo"</js>, <jv>bean</jv>);
129 *
130 *       <jc>// Execute it (by calling RestCallHandler.service(...) and then returning the response object).</jc>
131 *       RestResponse <jv>res</jv> = <jv>req</jv>.run();
132 *
133 *       <jc>// Run assertion tests on the results.</jc>
134 *       <jv>res</jv>.assertStatus().is(200);
135 *       <jv>res</jv>.assertContent().is(<js>"'foo'"</js>);
136 *
137 *       <jc>// Convert the content of the response to a bean.</jc>
138 *       <jv>bean</jv> = <jv>res</jv>.getContent().as(MyBean.<jk>class</jk>);
139 *    }
140 * </p>
141 *
142 * <p>
143 *    The <c>create(Object)</c> method can take in either <c>Class</c> objects or pre-instantiated beans.
144 *    The latter is particularly useful for testing Spring beans.
145 *
146 * <p>
147 *    The {@link MockRestRequest} object has convenience methods provided to allow you to set any properties
148 *    directly on the underlying {@link HttpServletRequest} object.  The following example shows how
149 *    this can be used to directly set roles on the request object to perform security testing.
150 *
151 * <h5 class='figure'>Example:</h5>
152 * <p class='bjava'>
153 *    <ja>@Rest</ja>(roleGuard=<js>"ADMIN"</js>)
154 *    <jk>public class</jk> A {
155 *       <ja>@RestGet</ja>
156 *       <jk>public</jk> String get() {
157 *          <jk>return</jk> <js>"OK"</js>;
158 *       }
159 *    }
160 *
161 *    <ja>@Test</ja>
162 *    <jk>public void</jk> mytest() <jk>throws</jk> Exception {
163 *       MockRestClient <jv>client</jv> = MockRestClient.<jsm>build</jsm>(A.<jk>class</jk>);
164 *
165 *       <jc>// Admin user should get 200, but anyone else should get 403-Unauthorized.</jc>
166 *       <jv>client</jv>.get().roles(<js>"ADMIN"</js>).run().assertStatus().is(200);
167 *       <jv>client</jv>.get().roles(<js>"USER"</js>).run().assertStatus().is(403);
168 *    }
169 * </p>
170 *
171 * <p>
172 *    Debug mode is provided that will cause your HTTP requests and responses to be sent to the console:
173 *
174 * <h5 class='figure'>Example:</h5>
175 * <p class='bjava'>
176 *    MockRestClient <jv>client</jv> = MockRestClient
177 *       .<jsm>create</jsm>(MyRest.<jk>class</jk>)
178 *       .debug()
179 *       .json5()
180 *       .build();
181 * </p>
182 *
183 * <p>
184 *    The class can also be used for testing of {@link Remote @Remote}-annotated interfaces against {@link Rest @Rest}-annotated resources.
185 *
186 * <h5 class='figure'>Example:</h5>
187 * <p class='bjava'>
188 *    <jc>// Our remote resource to test.</jc>
189 *    <ja>@Remote</ja>
190 *    <jk>public interface</jk> MyRemoteInterface {
191 *
192 *       <ja>@RemoteGet</ja>(<js>"/echoQuery"</js>)
193 *       <jk>public int</jk> echoQuery(<ja>@Query</ja>(name=<js>"id"</js>) <jk>int</jk> <jv>id</jv>);
194 *    }
195 *
196 *    <jc>// Our mocked-up REST interface to test against.</jc>
197 *    <ja>@Rest</ja>
198 *    <jk>public class</jk> MyRest {
199 *
200 *       <ja>@RestGet</ja>(path=<js>"/echoQuery"</js>)
201 *       <jk>public int</jk> echoQuery(<ja>@Query</ja>(<js>"id"</js>) String <jv>id</jv>) {
202 *          <jk>return</jk> <jv>id</jv>;
203 *       }
204 *    }
205 *
206 *    <ja>@Test</ja>
207 *    <jk>public void</jk> testProxy() {
208 *       <jc>// A mock client cannot apply the SSRF guard of @Remote calls, so it needs the allowPrivateUrls opt-in.</jc>
209 *       MyRemoteInterface <jv>client</jv> = MockRestClient
210 *          .create(MyRest.<jk>class</jk>)
211 *          .json()
212 *          .allowPrivateUrls(<jk>true</jk>)
213 *          .build()
214 *          .getRemote(MyRemoteInterface.<jk>class</jk>);
215 *
216 *       <jsm>assertEquals</jsm>(123, <jv>client</jv>.echoQuery(123));
217 *    }
218 * </p>
219 *
220 * <h5 class='section'>Notes:</h5><ul>
221 *    <li class='note'>This class is thread safe and reusable.
222 * </ul>
223 *
224 * <h5 class='section'>See Also:</h5><ul>
225 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestMock">juneau-rest-mock</a>
226 * </ul>
227 */
228@SuppressWarnings({
229   "java:S3740", // Raw Class/Builder types used intentionally for fluent mock client construction where response type is unknown at build time
230   "resource", // Builders and requests returned to callers; lifecycle managed by the enclosing MockRestClient or test
231   "unchecked" // Type erasure requires cast for builder chain
232})
233public class MockRestClient extends RestClient implements HttpClientConnection {
234
235   /**
236    * Builder class.
237    */
238   public static class Builder extends RestClient.Builder<Builder> implements BeanStoreOverridable<Builder> {
239
240      Object restBean;
241      String contextPath;
242      String servletPath;
243      RestContext restContext;
244      Map<String,String> pathVars;
245      BeanStore overridingBeanStore;
246
247      /**
248       * No-arg constructor.
249       *
250       * <p>
251       * Provided so that this class can be easily subclassed.
252       */
253      protected Builder() {
254         connectionManager(new MockHttpClientConnectionManager());
255      }
256
257      @Override /* Overridden from Context.Builder<?> */
258      public MockRestClient build() {
259         return super.build(MockRestClient.class);
260      }
261
262      /**
263       * Identifies the context path for the REST resource.
264       *
265       * <p>
266       *    This value is used to deconstruct the request URL and set the appropriate URL getters on the {@link HttpServletRequest}
267       *    object correctly.
268       *
269       * <p>
270       *    Should either be a value such as <js>"/foo"</js> or an empty string.
271       *
272       * <p>
273       *    The following fixes are applied to non-conforming strings.
274       * <ul>
275       *    <li><jk>nulls</jk> and <js>"/"</js> are converted to empty strings.
276       *    <li>Trailing slashes are trimmed.
277       *    <li>Leading slash is added if needed.
278       * </ul>
279       *
280       * @param value The context path.
281       * @return This object.
282       */
283      public Builder contextPath(String value) {
284         contextPath = toValidContextPath(value);
285         return this;
286      }
287
288      @Override /* Overridden from Context.Builder<?> */
289      public Builder copy() {
290         throw new NoSuchMethodError("Not implemented.");
291      }
292
293      @Override /* Overridden from Context.Builder<?> */
294      public Builder debug() {
295         super.debug();
296         return this;
297      }
298
299      @Override /* Overridden from Builder */
300      public Builder parsers(Class<? extends Parser>...value) {
301         super.parsers(value);
302         return this;
303      }
304
305      /**
306       * Add resolved path variables to this client.
307       *
308       * <p>
309       * Allows you to add resolved parent path variables when performing tests on child resource classes.
310       *
311       * <h5 class='section'>Example:</h5>
312       * <p class='bjava'>
313       *    <jc>// A parent class with a path variable.</jc>
314       *    <ja>@Rest</ja>(
315       *       path=<js>"/parent/{foo}"</js>,
316       *       children={
317       *          Child.<jk>class</jk>
318       *       }
319       *    )
320       *    <jk>public class</jk> Parent { ... }
321       *
322       *    <jc>// A child class that uses the parent path variable.</jc>
323       *    <ja>@Rest</ja>
324       *    <jk>public class</jk> Child {
325       *
326       *       <jk>@RestGet</jk>
327       *       <jk>public</jk> String get(<ja>@Path</ja>(<js>"foo"</js>) String <jv>foo</jv>) {
328       *          <jk>return</jk> <jv>foo</jv>;
329       *       }
330       *    }
331       * </p>
332       * <p class='bjava'>
333       *    <jc>// Test the method that uses the parent path variable.</jc>
334       *    MockRestClient
335       *       .<jsm>create</jsm>(Child.<jk>class</jk>)
336       *       .json5()
337       *       .pathVars(<js>"foo"</js>,<js>"bar"</js>)
338       *       .build()
339       *       .get(<js>"/"</js>)
340       *       .run()
341       *       .assertStatus().asCode().is(200)
342       *       .assertContent().is(<js>"bar"</js>);
343       * </p>
344       *
345       * <review>Needs review</review>
346       *
347       * @param value The path variables.
348       * @return This object.
349       * @see MockServletRequest#pathVars(Map)
350       */
351      public Builder pathVars(Map<String,String> value) {
352         pathVars = value;
353         return this;
354      }
355
356      /**
357       * Add resolved path variables to this client.
358       *
359       * <p>
360       * Identical to {@link #pathVars(Map)} but allows you to specify as a list of key/value pairs.
361       *
362       * @param pairs The key/value pairs.  Must be an even number of parameters.
363       * @return This object.
364       */
365      public Builder pathVars(String...pairs) {
366         return pathVars(mb(String.class, String.class).addPairs((Object[])pairs).build());
367      }
368
369      /**
370       * Specifies the {@link Rest}-annotated bean class or instance to test against.
371       *
372       * @param value The {@link Rest}-annotated bean class or instance.
373       * @return This object.
374       */
375      public Builder restBean(Object value) {
376         restBean = value;
377         return this;
378      }
379
380      /**
381       * Specifies the {@link RestContext} created for the REST bean.
382       *
383       * @param value The {@link RestContext} created for the REST bean.
384       * @return This object.
385       */
386      public Builder restContext(RestContext value) {
387         restContext = value;
388         return this;
389      }
390
391      /**
392       * Installs a {@link BeanStore BeanStore} as the
393       * {@code overridingParent} of the {@link RestContext}'s bean store, so test-time overrides
394       * (e.g. {@code TestBeanStore}) resolve at tier 1 of the lookup chain &mdash; above the
395       * resource's local {@code @Bean} factory entries.
396       *
397       * <p>
398       * Setting this disables the per-resource {@link RestContext} cache for the duration of this
399       * build, so the overlay is not silently shared with a previously-cached instance.
400       *
401       * @param value The override layer. Can be <jk>null</jk> to clear a previously-set value.
402       * @return This object.
403       *
404       * @since 10.0.0
405       */
406      @Override
407      public Builder overridingBeanStore(BeanStore value) {
408         overridingBeanStore = value;
409         return this;
410      }
411
412      @Override /* Overridden from Builder */
413      public Builder serializers(Class<? extends org.apache.juneau.marshall.serializer.Serializer>...value) {
414         super.serializers(value);
415         return this;
416      }
417
418      /**
419       * Identifies the servlet path for the REST resource.
420       *
421       * <p>
422       *    This value is used to deconstruct the request URL and set the appropriate URL getters on the {@link HttpServletRequest}
423       *    object correctly.
424       *
425       * <p>
426       *    Should either be a value such as <js>"/foo"</js> or an empty string.
427       *
428       * <p>
429       *    The following fixes are applied to non-conforming strings.
430       * <ul>
431       *    <li><jk>nulls</jk> and <js>"/"</js> are converted to empty strings.
432       *    <li>Trailing slashes are trimmed.
433       *    <li>Leading slash is added if needed.
434       * </ul>
435       *
436       * @param value The context path.
437       * @return This object.
438       */
439      public Builder servletPath(String value) {
440         servletPath = toValidContextPath(value);
441         return this;
442      }
443
444      /**
445       * Suppress logging on this client.
446       *
447       * @return This object.
448       */
449      public Builder suppressLogging() {
450         return logRequests(DetailLevel.NONE, null, null);
451      }
452
453
454   }
455
456   private static Map<Class<?>,RestContext> restContexts = new ConcurrentHashMap<>();
457
458   /**
459    * Creates a new {@link RestClient} with no registered serializer or parser.
460    *
461    * <p>
462    * Equivalent to calling:
463    * <p class='bjava'>
464    *    MockRestClient.<jsm>create</jsm>(<jv>impl</jv>).build();
465    * </p>
466    *
467    * @param impl
468    *    The REST bean or bean class annotated with {@link Rest @Rest}.
469    *    <br>If a class, it must have a no-arg constructor.
470    * @return A new client.
471    */
472   public static MockRestClient build(Object impl) {
473      return create(impl).build();
474   }
475
476   /**
477    * Creates a new {@link RestClient} with JSON marshalling support.
478    *
479    * <p>
480    * Equivalent to calling:
481    * <p class='bjava'>
482    *    MockRestClient.<jsm>create</jsm>(<jv>impl</jv>).json().build();
483    * </p>
484    *
485    * @param impl
486    *    The REST bean or bean class annotated with {@link Rest @Rest}.
487    *    <br>If a class, it must have a no-arg constructor.
488    * @return A new client.
489    */
490   public static MockRestClient buildJson(Object impl) {
491      return create(impl).json().build();
492   }
493
494   /**
495    * Creates a new {@link RestClient} with Simplified-JSON marshalling support.
496    *
497    * <p>
498    * Equivalent to calling:
499    * <p class='bjava'>
500    *    MockRestClient.<jsm>create</jsm>(<jv>impl</jv>).json().build();
501    * </p>
502    *
503    * @param impl
504    *    The REST bean or bean class annotated with {@link Rest @Rest}.
505    *    <br>If a class, it must have a no-arg constructor.
506    * @return A new client.
507    */
508   public static MockRestClient buildJson5(Object impl) {
509      return create(impl).json5().build();
510   }
511
512   /**
513    * Creates a new {@link RestClient} with Simplified-JSON marshalling support.
514    *
515    * <p>
516    * Same as {@link #buildJson5(Object)} but HTTP 400+ codes don't trigger {@link RestCallException RestCallExceptions}.
517    *
518    * <p>
519    * Equivalent to calling:
520    * <p class='bjava'>
521    *    MockRestClient.<jsm>create</jsm>(<jv>impl</jv>).json().ignoreErrors().build();
522    * </p>
523    *
524    * @param impl
525    *    The REST bean or bean class annotated with {@link Rest @Rest}.
526    *    <br>If a class, it must have a no-arg constructor.
527    * @return A new client.
528    */
529   public static MockRestClient buildJson5Lax(Object impl) {
530      return create(impl).json5().ignoreErrors().noTrace().build();
531   }
532
533   /**
534    * Creates a new {@link RestClient} with JSON marshalling support.
535    *
536    * <p>
537    * Same as {@link #buildJson(Object)} but HTTP 400+ codes don't trigger {@link RestCallException RestCallExceptions}.
538    *
539    * <p>
540    * Equivalent to calling:
541    * <p class='bjava'>
542    *    MockRestClient.<jsm>create</jsm>(<jv>impl</jv>).json().ignoreErrors().build();
543    * </p>
544    *
545    * @param impl
546    *    The REST bean or bean class annotated with {@link Rest @Rest}.
547    *    <br>If a class, it must have a no-arg constructor.
548    * @return A new client.
549    */
550   public static MockRestClient buildJsonLax(Object impl) {
551      return create(impl).json().ignoreErrors().noTrace().build();
552   }
553
554   /**
555    * Creates a new {@link RestClient} with no registered serializer or parser.
556    *
557    * <p>
558    * Same as {@link #build(Object)} but HTTP 400+ codes don't trigger {@link RestCallException RestCallExceptions}.
559    *
560    * <p>
561    * Equivalent to calling:
562    * <p class='bjava'>
563    *    MockRestClient.<jsm>create</jsm>(<jv>impl</jv>).ignoreErrors().noTrace().build();
564    * </p>
565    *
566    * @param impl
567    *    The REST bean or bean class annotated with {@link Rest @Rest}.
568    *    <br>If a class, it must have a no-arg constructor.
569    * @return A new client.
570    */
571   public static MockRestClient buildLax(Object impl) {
572      return create(impl).ignoreErrors().noTrace().build();
573   }
574
575   /**
576    * Creates a new {@link org.apache.juneau.rest.client.classic.RestClient.Builder RestClient.Builder}configured with the specified REST implementation bean or bean class.
577    *
578    * @param impl
579    *    The REST bean or bean class annotated with {@link Rest @Rest}.
580    *    <br>If a class, it must have a no-arg constructor.
581    * @return A new builder.
582    */
583   public static Builder create(Object impl) {
584      return new Builder().restBean(impl);
585   }
586
587   /**
588    * Creates a new {@link org.apache.juneau.rest.client.classic.RestClient.Builder RestClient.Builder}configured with the specified REST implementation bean or bean class.
589    *
590    * <p>
591    * Same as {@link #create(Object)} but HTTP 400+ codes don't trigger {@link RestCallException RestCallExceptions}.
592    *
593    * @param impl
594    *    The REST bean or bean class annotated with {@link Rest @Rest}.
595    *    <br>If a class, it must have a no-arg constructor.
596    * @return A new builder.
597    */
598   public static Builder createLax(Object impl) {
599      return new Builder().restBean(impl).ignoreErrors().noTrace();
600   }
601
602   private static Builder preInit(Builder builder) {
603      try {
604         var restBean = builder.restBean;
605         var contextPath = builder.contextPath;
606         var servletPath = builder.servletPath;
607         var overlay = builder.overridingBeanStore;
608
609         var isClass = restBean instanceof Class;
610         // Only class-based contexts are cached; a caller-supplied instance always gets its own context.
611         var cacheable = isClass && overlay == null;
612         var restBeanCtx = cacheable ? restContexts.get(restBean) : null;
613         if (restBeanCtx == null) {
614            var o = isClass ? ((Class<?>)restBean).getDeclaredConstructor().newInstance() : restBean;
615            restBeanCtx = new RestContext(new RestContext.Args(o.getClass(), null, null, () -> o, "", bs -> {
616               bs.addBeanType(RestDebugFormatter.class, BasicTestRestDebugFormatter.class);
617            }, overlay, null, RestContext.ContextKind.ROOT)).postInit().postInitChildFirst();
618            if (cacheable)
619               restContexts.put((Class<?>)restBean, restBeanCtx);
620         }
621         builder.restContext(restBeanCtx);
622
623         if (servletPath == null)
624            servletPath = toValidContextPath(restBeanCtx.getFullPath());
625
626         final var suffix = emptyIfNull(contextPath) + emptyIfNull(servletPath);
627         final var existingSupplier = builder.getRootUrlSupplier();
628         if (existingSupplier != null) {
629            // Compose a new supplier that appends the fixed path suffix to whatever the original supplier returns.
630            builder.rootUrl(() -> existingSupplier.get() + suffix);
631         } else {
632            builder.rootUrl("http://localhost" + suffix);
633         }
634
635         builder.servletPath = servletPath;
636         return builder;
637      } catch (Exception e) {
638         throw new ConfigException(e, "Could not initialize MockRestClient");
639      }
640   }
641
642   private final RestContext restContext;
643   private final Object restObject;
644   private final String contextPath;
645   private final String servletPath;
646
647   private final Map<String,String> pathVars;
648   private final ThreadLocal<HttpRequest> rreq = new ThreadLocal<>();
649   private final ThreadLocal<MockRestResponse> rres = new ThreadLocal<>();
650   private final ThreadLocal<MockServletRequest> sreq = new ThreadLocal<>();
651
652   private final ThreadLocal<MockServletResponse> sres = new ThreadLocal<>();
653
654   /**
655    * Constructor.
656    *
657    * @param builder
658    *    The builder for this object.
659    */
660   public MockRestClient(Builder builder) {
661      super(preInit(builder));
662      restContext = builder.restContext;
663      contextPath = nn(builder.contextPath) ? builder.contextPath : "";
664      servletPath = nn(builder.servletPath) ? builder.servletPath : "";
665      pathVars = nn(builder.pathVars) ? builder.pathVars : emptyMap();
666      restObject = restContext.getResource();
667
668      HttpClientConnectionManager ccm = getHttpClientConnectionManager();
669      if (ccm instanceof MockHttpClientConnectionManager ccm2)
670         ccm2.init(this);
671   }
672
673   @Override /* Overridden from RestClient */
674   public MockRestRequest callback(String callString) throws RestCallException {
675      return (MockRestRequest)super.callback(callString);
676   }
677
678   @Override /* Overridden from HttpClientConnection */
679   public void close() throws IOException {
680      // Don't call super.close() because it will close the client.
681      rreq.remove();
682      rres.remove();
683      sreq.remove();
684      sres.remove();
685   }
686
687   @Override /* Overridden from RestClient */
688   public MockRestRequest delete(Object url) throws RestCallException {
689      return (MockRestRequest)super.delete(url);
690   }
691
692   @Override /* Overridden from HttpClientConnection */
693   public void flush() throws IOException {
694      // No-op: Mock implementation - full functionality not required
695   }
696
697   @Override /* Overridden from RestClient */
698   public MockRestRequest formPost(Object url) throws RestCallException {
699      return (MockRestRequest)super.formPost(url);
700   }
701
702   @Override /* Overridden from RestClient */
703   public MockRestRequest formPost(Object url, Object body) throws RestCallException {
704      return (MockRestRequest)super.formPost(url, body);
705   }
706
707   @Override /* Overridden from RestClient */
708   public MockRestRequest formPostPairs(Object url, String...parameters) throws RestCallException {
709      return (MockRestRequest)super.formPostPairs(url, parameters);
710   }
711
712   @Override /* Overridden from RestClient */
713   public MockRestRequest get() throws RestCallException {
714      return (MockRestRequest)super.get();
715   }
716
717   @Override /* Overridden from RestClient */
718   public MockRestRequest get(Object url) throws RestCallException {
719      return (MockRestRequest)super.get(url);
720   }
721
722   /**
723    * Returns the current client-side REST request.
724    *
725    * <p>
726    * Note that this uses a {@link ThreadLocal} object for storage and so will not work on requests executed in
727    * separate threads such as when using {@link Future Futures}.
728    *
729    * @return The current client-side REST request, or <jk>null</jk> if not set.
730    */
731   public HttpRequest getCurrentClientRequest() { return rreq.get(); }
732
733   /**
734    * Returns the current client-side REST response.
735    *
736    * <p>
737    * Note that this uses a {@link ThreadLocal} object for storage and so will not work on requests executed in
738    * separate threads such as when using {@link Future Futures}.
739    *
740    * @return The current client-side REST response, or <jk>null</jk> if not set.
741    */
742   public MockRestResponse getCurrentClientResponse() { return rres.get(); }
743
744   /**
745    * Returns the current server-side REST request.
746    *
747    * <p>
748    * Note that this uses a {@link ThreadLocal} object for storage and so will not work on requests executed in
749    * separate threads such as when using {@link Future Futures}.
750    *
751    * @return The current server-side REST request, or <jk>null</jk> if not set.
752    */
753   public MockServletRequest getCurrentServerRequest() { return sreq.get(); }
754
755   /**
756    * Returns the current server-side REST response.
757    *
758    * <p>
759    * Note that this uses a {@link ThreadLocal} object for storage and so will not work on requests executed in
760    * separate threads such as when using {@link Future Futures}.
761    *
762    * @return The current server-side REST response, or <jk>null</jk> if not set.
763    */
764   public MockServletResponse getCurrentServerResponse() { return sres.get(); }
765
766   @Override /* Overridden from HttpClientConnection */
767   public HttpConnectionMetrics getMetrics() { return null; }
768
769   @Override /* Overridden from HttpClientConnection */
770   public int getSocketTimeout() { return Integer.MAX_VALUE; }
771
772   @Override /* Overridden from RestClient */
773   public MockRestRequest head(Object url) throws RestCallException {
774      return (MockRestRequest)super.head(url);
775   }
776
777   @Override /* Overridden from HttpClientConnection */
778   public boolean isOpen() { return true; }
779
780   @Override /* Overridden from HttpClientConnection */
781   public boolean isResponseAvailable(int timeout) throws IOException {
782      return true;
783   }
784
785   @Override /* Overridden from HttpClientConnection */
786   public boolean isStale() { return false; }
787
788   @Override /* Overridden from RestClient */
789   public MockRestRequest options(Object url) throws RestCallException {
790      return (MockRestRequest)super.options(url);
791   }
792
793   @Override /* Overridden from RestClient */
794   public MockRestRequest patch(Object url) throws RestCallException {
795      return (MockRestRequest)super.patch(url);
796   }
797
798   @Override /* Overridden from RestClient */
799   public MockRestRequest patch(Object url, Object body) throws RestCallException {
800      return (MockRestRequest)super.patch(url, body);
801   }
802
803   @Override /* Overridden from RestClient */
804   public MockRestRequest patch(Object url, String body, ContentType contentType) throws RestCallException {
805      return (MockRestRequest)super.patch(url, body, contentType);
806   }
807
808   @Override /* Overridden from RestClient */
809   public MockRestRequest post(Object url) throws RestCallException {
810      return (MockRestRequest)super.post(url);
811   }
812
813   @Override /* Overridden from RestClient */
814   public MockRestRequest post(Object url, Object body) throws RestCallException {
815      return (MockRestRequest)super.post(url, body);
816   }
817
818   @Override /* Overridden from RestClient */
819   public MockRestRequest post(Object url, String body, ContentType contentType) throws RestCallException {
820      return (MockRestRequest)super.post(url, body, contentType);
821   }
822
823   @Override /* Overridden from RestClient */
824   public MockRestRequest put(Object url) throws RestCallException {
825      return (MockRestRequest)super.put(url);
826   }
827
828   @Override /* Overridden from RestClient */
829   public MockRestRequest put(Object url, Object body) throws RestCallException {
830      return (MockRestRequest)super.put(url, body);
831   }
832
833   @Override /* Overridden from RestClient */
834   public MockRestRequest put(Object url, String body, ContentType contentType) throws RestCallException {
835      return (MockRestRequest)super.put(url, body, contentType);
836   }
837
838   @Override /* Overridden from HttpClientConnection */
839   public void receiveResponseEntity(HttpResponse response) throws HttpException, IOException {
840      InputStream is = new ByteArrayInputStream(sres.get().getContent());
841      var contentEncoding = response.getLastHeader("Content-Encoding");
842      if (nn(contentEncoding) && eqic(contentEncoding.getValue(), "gzip"))
843         is = new GZIPInputStream(is);
844      response.setEntity(new InputStreamEntity(is));
845   }
846
847   @Override /* Overridden from HttpClientConnection */
848   public HttpResponse receiveResponseHeader() throws HttpException, IOException {
849      try {
850         var res = MockServletResponse.create();
851         var logLevel = sreq.get().getLogLevel();
852         if (logLevel != null) {
853            var target = Logger.getLogger(restObject.getClass().getName());
854            var prevLevel = target.getLevel();
855            target.setLevel(logLevel);
856            try {
857               restContext.execute(restObject, sreq.get(), res);
858            } finally {
859               target.setLevel(prevLevel);
860            }
861         } else {
862            restContext.execute(restObject, sreq.get(), res);
863         }
864
865         // A bug in HttpClient causes an infinite loop if the response is less than 200.
866         // As a workaround, just add 1000 to the status code (which is better than an infinite loop).
867         if (res.getStatus() < 200)
868            res.setStatus(1000 + res.getStatus());
869
870         sres.set(res);
871
872         var response = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, res.getStatus(), res.getMessage()));
873         res.getHeaders().forEach((k, v) -> {
874            for (var hv : v)
875               response.addHeader(k, hv);
876         });
877
878         return response;
879      } catch (Exception e) {
880         throw new HttpException(emptyIfNull(e.getMessage()), e);
881      }
882   }
883
884   @Override /* Overridden from RestClient */
885   public MockRestRequest request(RestOperation op) throws RestCallException {
886      return (MockRestRequest)super.request(op);
887   }
888
889   @Override /* Overridden from RestClient */
890   public MockRestRequest request(String method, Object url) throws RestCallException {
891      return (MockRestRequest)super.request(method, url);
892   }
893
894   @Override /* Overridden from RestClient */
895   public MockRestRequest request(String method, Object url, boolean hasBody) throws RestCallException {
896      return (MockRestRequest)super.request(method, url, hasBody);
897   }
898
899   @Override /* Overridden from RestClient */
900   public MockRestRequest request(String method, Object url, Object body) throws RestCallException {
901      return (MockRestRequest)super.request(method, url, body);
902   }
903
904   @Override /* Overridden from HttpClientConnection */
905   public void sendRequestEntity(HttpEntityEnclosingRequest request) throws HttpException, IOException {
906      byte[] body = {};
907      var entity = request.getEntity();
908      if (nn(entity)) {
909         var length = entity.getContentLength();
910         if (length < 0)
911            length = 1024;
912         var baos = new ByteArrayOutputStream((int)Math.min(length, 1024));
913         entity.writeTo(baos);
914         baos.flush();
915         body = baos.toByteArray();
916      }
917      sreq.get().content(body);
918   }
919
920   @Override /* Overridden from HttpClientConnection */
921   public void sendRequestHeader(HttpRequest request) throws HttpException, IOException {
922      try {
923         var rl = request.getRequestLine();
924         var path = rl.getUri();
925         var target = findTarget(request);
926
927         var req = findRestRequest(request);
928         rreq.set(req);
929         rres.remove();
930         sreq.remove();
931         sres.remove();
932
933         path = target + path;
934
935         var pr = new MockPathResolver(target, contextPath, servletPath, path, null);
936         if (nn(pr.getError()))
937            throw new IllegalStateException(pr.getError());
938
939         var r = MockServletRequest.create(request.getRequestLine().getMethod(), pr.getURI()).contextPath(pr.getContextPath()).servletPath(pr.getServletPath()).pathVars(pathVars).logLevel(isDebug() ? Level.FINEST : null);
940
941         for (var h : request.getAllHeaders())
942            r.header(h.getName(), h.getValue());
943
944         sreq.set(r);
945         sreq.get().applyOverrides(req);
946      } catch (Exception e) {
947         throw new HttpException(e.getMessage(), e);
948      }
949   }
950
951   @Override /* Overridden from HttpClientConnection */
952   public void setSocketTimeout(int timeout) {
953      // No-op: Mock implementation - full functionality not required
954   }
955
956   @Override /* Overridden from HttpClientConnection */
957   public void shutdown() throws IOException {
958      // No-op: Mock implementation - full functionality not required
959   }
960
961   /**
962    * Attempts to unwrap the request to find the underlying RestRequest object.
963    * Returns the same object if one of the low-level client methods are used (e.g. execute(HttpUriRequest)).
964    */
965   private HttpRequest findRestRequest(HttpRequest req) {
966      if (req instanceof RestRequestCreated req2)
967         return req2.getRestRequest();
968      if (req instanceof HttpRequestWrapper req2)
969         return findRestRequest(req2.getOriginal());
970      return req;
971   }
972
973   private static String findTarget(HttpRequest req) {
974      if (req instanceof HttpRequestWrapper req2) {
975         var httpHost = req2.getTarget();
976         if (nn(httpHost))
977            return httpHost.toURI();
978      }
979      return "http://localhost";
980   }
981
982   @Override /* Overridden from RestClient */
983   protected MockRestRequest createRequest(URI uri, String method, boolean hasBody) throws RestCallException {
984      return new MockRestRequest(this, uri, method, hasBody);
985   }
986
987   @Override /* Overridden from RestClient */
988   protected MockRestResponse createResponse(RestRequest req, HttpResponse httpResponse, Parser parser) throws RestCallException {
989      return new MockRestResponse(this, req, httpResponse, parser);
990   }
991
992   MockRestClient currentResponse(MockRestResponse value) {
993      rres.set(value);
994      return this;
995   }
996}