diff --git a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java index 59fb75c20..b09b474a5 100644 --- a/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java +++ b/extensions/amazon-lambda-http/runtime/src/main/java/io/quarkus/amazon/lambda/http/LambdaHttpHandler.java @@ -1,6 +1,7 @@ package io.quarkus.amazon.lambda.http; import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -37,7 +38,14 @@ public class LambdaHttpHandler implements RequestHandler queue = new LinkedBlockingQueue<>(); protected boolean connected = true; protected VirtualChannel peer; - VirtualClientConnection(VirtualAddress address) { - this.address = address; + VirtualClientConnection(SocketAddress clientAddress) { + this.clientAddress = clientAddress; } - public VirtualAddress clientAddress() { - return address; + public SocketAddress clientAddress() { + return clientAddress; } /** @@ -104,6 +105,21 @@ public class VirtualClientConnection { * @return */ public static VirtualClientConnection connect(final VirtualAddress remoteAddress) { + return connect(remoteAddress, remoteAddress); + + } + + /** + * Establish a virtual intra-JVM connection + * + * @param remoteAddress + * @param clientAddress + * @return + */ + public static VirtualClientConnection connect(VirtualAddress remoteAddress, SocketAddress clientAddress) { + if (clientAddress == null) + clientAddress = remoteAddress; + Channel boundChannel = VirtualChannelRegistry.get(remoteAddress); if (boundChannel == null) { throw new RuntimeException("No virtual channel available"); @@ -113,9 +129,8 @@ public class VirtualClientConnection { } VirtualServerChannel serverChannel = (VirtualServerChannel) boundChannel; - VirtualClientConnection conn = new VirtualClientConnection(remoteAddress); + VirtualClientConnection conn = new VirtualClientConnection(clientAddress); conn.peer = serverChannel.serve(conn); return conn; - } } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java index ccbfed052..a73732dd9 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java @@ -24,6 +24,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.net.SocketAddress; import io.vertx.ext.web.RoutingContext; /** @@ -95,8 +96,12 @@ public class VertxRequestHandler implements Handler { HttpServerResponse response = request.response(); VertxHttpResponse vertxResponse = new VertxHttpResponse(request, dispatcher.getProviderFactory(), request.method(), allocator, output); + // client address may not be available with VirtualHttp + SocketAddress socketAddress = request.remoteAddress(); + String host = socketAddress != null ? socketAddress.host() : null; + VertxHttpRequest vertxRequest = new VertxHttpRequest(ctx, headers, uriInfo, request.rawMethod(), - request.remoteAddress().host(), dispatcher.getDispatcher(), vertxResponse, false); + host, dispatcher.getDispatcher(), vertxResponse, false); vertxRequest.setInputStream(is); try { ResteasyContext.pushContext(SecurityContext.class, new QuarkusResteasySecurityContext(request)); diff --git a/integration-tests/amazon-lambda-http-resteasy/pom.xml b/integration-tests/amazon-lambda-http-resteasy/pom.xml new file mode 100644 index 000000000..ab618e48d --- /dev/null +++ b/integration-tests/amazon-lambda-http-resteasy/pom.xml @@ -0,0 +1,116 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-amazon-lambda-http-resteasy + Quarkus - Integration Tests - Amazon Lambda HTTP Resteasy + Test with Resteasy Standalone and Amazon Lambda HTTP + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-amazon-lambda-http + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-amazon-lambda + test + + + + + + + src/main/resources + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + io.quarkus + quarkus-maven-plugin + + + native-image + + native-image + + + false + true + true + ${graalvmHome} + false + false + + + + + + + + + + + diff --git a/integration-tests/amazon-lambda-http-resteasy/src/main/java/io/quarkus/it/amazon/lambda/GreetingResource.java b/integration-tests/amazon-lambda-http-resteasy/src/main/java/io/quarkus/it/amazon/lambda/GreetingResource.java new file mode 100644 index 000000000..bd8fe65f6 --- /dev/null +++ b/integration-tests/amazon-lambda-http-resteasy/src/main/java/io/quarkus/it/amazon/lambda/GreetingResource.java @@ -0,0 +1,43 @@ +package io.quarkus.it.amazon.lambda; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } + + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN) + public String hello(String name) { + return "hello " + name; + } + + @POST + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + public byte[] hello(byte[] bytes) { + if (bytes[0] != 0 || bytes[1] != 1 || bytes[2] != 2 || bytes[3] != 3) { + throw new RuntimeException("bad input"); + } + byte[] rtn = { 4, 5, 6 }; + return rtn; + } + + @POST + @Path("empty") + public void empty() { + + } + +} diff --git a/integration-tests/amazon-lambda-http-resteasy/src/main/resources/application.properties b/integration-tests/amazon-lambda-http-resteasy/src/main/resources/application.properties new file mode 100644 index 000000000..6156f0357 --- /dev/null +++ b/integration-tests/amazon-lambda-http-resteasy/src/main/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.lambda.enable-polling-jvm-mode=true +quarkus.http.virtual=true diff --git a/integration-tests/amazon-lambda-http-resteasy/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleIT.java b/integration-tests/amazon-lambda-http-resteasy/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleIT.java new file mode 100644 index 000000000..7bf1a2732 --- /dev/null +++ b/integration-tests/amazon-lambda-http-resteasy/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.amazon.lambda; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class AmazonLambdaSimpleIT extends AmazonLambdaSimpleTestCase { +} diff --git a/integration-tests/amazon-lambda-http-resteasy/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java b/integration-tests/amazon-lambda-http-resteasy/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java new file mode 100644 index 000000000..80f207ec4 --- /dev/null +++ b/integration-tests/amazon-lambda-http-resteasy/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java @@ -0,0 +1,99 @@ +package io.quarkus.it.amazon.lambda; + +import javax.ws.rs.core.MediaType; + +import org.apache.commons.codec.binary.Base64; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.amazon.lambda.http.model.AwsProxyRequest; +import io.quarkus.amazon.lambda.http.model.AwsProxyResponse; +import io.quarkus.amazon.lambda.http.model.Headers; +import io.quarkus.amazon.lambda.test.LambdaClient; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class AmazonLambdaSimpleTestCase { + + @Test + public void testGetText() throws Exception { + testGetText("/hello"); + } + + private String body(AwsProxyResponse response) { + if (!response.isBase64Encoded()) + return response.getBody(); + return new String(Base64.decodeBase64(response.getBody())); + } + + private void testGetText(String path) { + AwsProxyRequest request = new AwsProxyRequest(); + request.setHttpMethod("GET"); + request.setPath(path); + AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); + Assertions.assertEquals(out.getStatusCode(), 200); + Assertions.assertEquals(body(out), "hello"); + Assertions.assertTrue(out.getMultiValueHeaders().getFirst("Content-Type").startsWith("text/plain")); + } + + @Test + public void test404() throws Exception { + AwsProxyRequest request = new AwsProxyRequest(); + request.setHttpMethod("GET"); + request.setPath("/nowhere"); + AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); + Assertions.assertEquals(out.getStatusCode(), 404); + } + + @Test + public void testPostText() throws Exception { + testPostText("/hello"); + } + + private void testPostText(String path) { + AwsProxyRequest request = new AwsProxyRequest(); + request.setHttpMethod("POST"); + request.setMultiValueHeaders(new Headers()); + request.getMultiValueHeaders().add("Content-Type", "text/plain"); + request.setPath(path); + request.setBody("Bill"); + AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); + Assertions.assertEquals(out.getStatusCode(), 200); + Assertions.assertEquals(body(out), "hello Bill"); + Assertions.assertTrue(out.getMultiValueHeaders().getFirst("Content-Type").startsWith("text/plain")); + } + + @Test + public void testPostBinary() throws Exception { + AwsProxyRequest request = new AwsProxyRequest(); + byte[] bytes = { 0, 1, 2, 3 }; + String body = Base64.encodeBase64String(bytes); + request.setHttpMethod("POST"); + request.setMultiValueHeaders(new Headers()); + request.getMultiValueHeaders().add("Content-Type", MediaType.APPLICATION_OCTET_STREAM); + request.setPath("/hello"); + request.setBody(body); + request.setIsBase64Encoded(true); + AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); + Assertions.assertEquals(out.getStatusCode(), 200); + Assertions.assertEquals(out.getMultiValueHeaders().getFirst("Content-Type"), MediaType.APPLICATION_OCTET_STREAM); + Assertions.assertTrue(out.isBase64Encoded()); + byte[] rtn = Base64.decodeBase64(out.getBody()); + Assertions.assertEquals(rtn[0], 4); + Assertions.assertEquals(rtn[1], 5); + Assertions.assertEquals(rtn[2], 6); + + } + + @Test + public void testPostEmpty() throws Exception { + AwsProxyRequest request = new AwsProxyRequest(); + request.setHttpMethod("POST"); + request.setMultiValueHeaders(new Headers()); + request.setPath("/hello/empty"); + AwsProxyResponse out = LambdaClient.invoke(AwsProxyResponse.class, request); + Assertions.assertEquals(out.getStatusCode(), 204); + + } + +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 71c6f558f..dd9ad175b 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -61,6 +61,7 @@ test-extension amazon-lambda amazon-lambda-http-it + amazon-lambda-http-resteasy kogito kogito-maven kubernetes @@ -73,6 +74,7 @@ resteasy-jackson jgit virtual-http + virtual-http-resteasy artemis-core artemis-jms maven diff --git a/integration-tests/virtual-http-resteasy/pom.xml b/integration-tests/virtual-http-resteasy/pom.xml new file mode 100644 index 000000000..2a273ebda --- /dev/null +++ b/integration-tests/virtual-http-resteasy/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + + quarkus-integration-test-virtual-http-resteasy + Quarkus - Integration Tests - Virtual Http Resteasy Standalone + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-azure-functions-http + + + com.microsoft.azure.functions + azure-functions-java-library + test + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + diff --git a/integration-tests/virtual-http-resteasy/src/main/java/io/quarkus/it/virtual/GreetingResource.java b/integration-tests/virtual-http-resteasy/src/main/java/io/quarkus/it/virtual/GreetingResource.java new file mode 100644 index 000000000..f3757e600 --- /dev/null +++ b/integration-tests/virtual-http-resteasy/src/main/java/io/quarkus/it/virtual/GreetingResource.java @@ -0,0 +1,25 @@ +package io.quarkus.it.virtual; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } + + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN) + public String hello(String name) { + return "hello " + name; + } +} diff --git a/integration-tests/virtual-http-resteasy/src/main/resources/META-INF/beans.xml b/integration-tests/virtual-http-resteasy/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/integration-tests/virtual-http-resteasy/src/main/resources/META-INF/beans.xml @@ -0,0 +1,2 @@ + + diff --git a/integration-tests/virtual-http-resteasy/src/main/resources/application.properties b/integration-tests/virtual-http-resteasy/src/main/resources/application.properties new file mode 100644 index 000000000..2b68e232f --- /dev/null +++ b/integration-tests/virtual-http-resteasy/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.http.virtual=true diff --git a/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java new file mode 100644 index 000000000..ed41833c3 --- /dev/null +++ b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/FunctionTest.java @@ -0,0 +1,140 @@ +package io.quarkus.it.virtual; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.logging.Logger; + +import javax.ws.rs.core.MediaType; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; + +import io.quarkus.azure.functions.resteasy.runtime.Function; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +/** + * Unit test for Function class. + */ +@QuarkusTest +public class FunctionTest { + @Test + public void testJaxrs() throws Exception { + String uri = "https://foo.com/hello"; + testGET(uri); + testPOST(uri); + } + + @Test + public void testNotFound() { + final HttpRequestMessageMock req = new HttpRequestMessageMock(); + req.setUri(URI.create("https://nowhere.com/badroute")); + req.setHttpMethod(HttpMethod.GET); + + // Invoke + final HttpResponseMessage ret = new Function().run(req, new ExecutionContext() { + @Override + public Logger getLogger() { + return null; + } + + @Override + public String getInvocationId() { + return null; + } + + @Override + public String getFunctionName() { + return null; + } + }); + + // Verify + Assertions.assertEquals(ret.getStatus(), HttpStatus.NOT_FOUND); + } + + @Test + public void testHttp() { + // assure that socket is created in dev/test mode + RestAssured.when().get("/hello").then() + .contentType("text/plain") + .body(equalTo("hello")); + + RestAssured.given().contentType("text/plain").body("Bill").post("/hello").then() + .contentType("text/plain") + .body(containsString("hello Bill")); + } + + private void testGET(String uri) { + final HttpRequestMessageMock req = new HttpRequestMessageMock(); + req.setUri(URI.create(uri)); + req.setHttpMethod(HttpMethod.GET); + + // Invoke + final HttpResponseMessage ret = new Function().run(req, new ExecutionContext() { + @Override + public Logger getLogger() { + return null; + } + + @Override + public String getInvocationId() { + return null; + } + + @Override + public String getFunctionName() { + return null; + } + }); + + // Verify + Assertions.assertEquals(ret.getStatus(), HttpStatus.OK); + Assertions.assertEquals("hello", new String((byte[]) ret.getBody(), StandardCharsets.UTF_8)); + String contentType = ret.getHeader("Content-Type"); + Assertions.assertNotNull(contentType); + Assertions.assertTrue(MediaType.valueOf(contentType).isCompatible(MediaType.TEXT_PLAIN_TYPE)); + } + + private void testPOST(String uri) { + final HttpRequestMessageMock req = new HttpRequestMessageMock(); + req.setUri(URI.create(uri)); + req.setHttpMethod(HttpMethod.POST); + req.setBody("Bill".getBytes()); + req.getHeaders().put("Content-Type", "text/plain"); + + // Invoke + final HttpResponseMessage ret = new Function().run(req, new ExecutionContext() { + @Override + public Logger getLogger() { + return null; + } + + @Override + public String getInvocationId() { + return null; + } + + @Override + public String getFunctionName() { + return null; + } + }); + + // Verify + Assertions.assertEquals(ret.getStatus(), HttpStatus.OK); + Assertions.assertEquals("hello Bill", new String((byte[]) ret.getBody(), StandardCharsets.UTF_8)); + String contentType = ret.getHeader("Content-Type"); + Assertions.assertNotNull(contentType); + Assertions.assertTrue(MediaType.valueOf(contentType).isCompatible(MediaType.TEXT_PLAIN_TYPE)); + } + +} diff --git a/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/HttpRequestMessageMock.java b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/HttpRequestMessageMock.java new file mode 100644 index 000000000..583fef6dc --- /dev/null +++ b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/HttpRequestMessageMock.java @@ -0,0 +1,75 @@ +package io.quarkus.it.virtual; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.HttpStatusType; + +public class HttpRequestMessageMock implements HttpRequestMessage> { + protected URI uri; + protected HttpMethod httpMethod; + protected Map headers = new HashMap<>(); + protected Map queryParameters = new HashMap<>(); + protected byte[] body; + + @Override + public URI getUri() { + return uri; + } + + @Override + public HttpMethod getHttpMethod() { + return httpMethod; + } + + @Override + public Map getHeaders() { + return headers; + } + + @Override + public Map getQueryParameters() { + return queryParameters; + } + + @Override + public Optional getBody() { + return Optional.ofNullable(body); + } + + @Override + public HttpResponseMessage.Builder createResponseBuilder(HttpStatus httpStatus) { + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(httpStatus); + } + + @Override + public HttpResponseMessage.Builder createResponseBuilder(HttpStatusType httpStatusType) { + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(httpStatusType); + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public void setHttpMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public void setQueryParameters(Map queryParameters) { + this.queryParameters = queryParameters; + } + + public void setBody(byte[] body) { + this.body = body; + } +} diff --git a/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/HttpResponseMessageMock.java b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/HttpResponseMessageMock.java new file mode 100644 index 000000000..f5db3aaad --- /dev/null +++ b/integration-tests/virtual-http-resteasy/src/test/java/io/quarkus/it/virtual/HttpResponseMessageMock.java @@ -0,0 +1,83 @@ +package io.quarkus.it.virtual; + +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.HttpStatusType; + +/** + * The mock for HttpResponseMessage, can be used in unit tests to verify if the + * returned response by HTTP trigger function is correct or not. + */ +public class HttpResponseMessageMock implements HttpResponseMessage { + private int httpStatusCode; + private HttpStatusType httpStatus; + private Object body; + private Map headers; + + public HttpResponseMessageMock(final HttpStatusType status, final Map headers, final Object body) { + this.httpStatus = status; + this.httpStatusCode = status.value(); + this.headers = headers; + this.body = body; + } + + @Override + public HttpStatusType getStatus() { + return this.httpStatus; + } + + @Override + public int getStatusCode() { + return httpStatusCode; + } + + @Override + public String getHeader(String key) { + return this.headers.get(key); + } + + @Override + public Object getBody() { + return this.body; + } + + public static class HttpResponseMessageBuilderMock implements HttpResponseMessage.Builder { + private Object body; + private int httpStatusCode; + private Map headers = new HashMap<>(); + private HttpStatusType httpStatus; + + public Builder status(HttpStatus status) { + this.httpStatusCode = status.value(); + this.httpStatus = status; + return this; + } + + @Override + public Builder status(final HttpStatusType httpStatusType) { + this.httpStatusCode = httpStatusType.value(); + this.httpStatus = httpStatusType; + return this; + } + + @Override + public HttpResponseMessage.Builder header(final String key, final String value) { + this.headers.put(key, value); + return this; + } + + @Override + public HttpResponseMessage.Builder body(final Object body) { + this.body = body; + return this; + } + + @Override + public HttpResponseMessage build() { + return new HttpResponseMessageMock(this.httpStatus, this.headers, this.body); + } + } +}