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 — 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}