You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2018/08/29 12:06:59 UTC

[juneau] branch master updated: Implement @Response beans in RestClient.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 00715cc  Implement @Response beans in RestClient.
00715cc is described below

commit 00715cc5832415e24e3e0c367b662ad7bd44d3e9
Author: JamesBognar <ja...@apache.org>
AuthorDate: Wed Aug 29 08:06:20 2018 -0400

    Implement @Response beans in RestClient.
---
 .../httppart/bean/ResponseBeanPropertyMeta.java    |  2 +-
 .../09.juneau-rest-client/01.RestProxies.html      |  2 -
 .../01.RestProxies/09.Response.html                | 16 ++---
 .../org/apache/juneau/rest/client/RestCall.java    | 71 ++++++++++++++++------
 .../org/apache/juneau/rest/client/RestClient.java  |  4 +-
 ...moteReturn.java => RemoteMethodBeanReturn.java} | 44 +++++++++++---
 .../rest/client/remote/RemoteMethodMeta.java       |  4 +-
 .../rest/client/remote/RemoteMethodReturn.java     | 39 ++++--------
 .../juneau/rest/client/remote/RemoteReturn.java    |  3 +
 .../client/remote/RemoteMethodAnnotationTest.java  |  1 -
 .../rest/client/remote/RequestAnnotationTest.java  | 66 +++++++++++++++++---
 .../rest/client/remote/ResponseAnnotationTest.java | 35 ++++++++---
 12 files changed, 198 insertions(+), 89 deletions(-)

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanPropertyMeta.java
index 6789045..7be0e39 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanPropertyMeta.java
@@ -53,7 +53,7 @@ public class ResponseBeanPropertyMeta {
 
 	static class Builder {
 		HttpPartType partType;
-		HttpPartSchema schema;
+		HttpPartSchema schema = HttpPartSchema.DEFAULT;
 		String name;
 		Method getter;
 		PropertyStore ps = PropertyStore.DEFAULT;
diff --git a/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies.html b/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies.html
index 43cf50f..1e67206 100644
--- a/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies.html
+++ b/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies.html
@@ -48,8 +48,6 @@
 		<li class='ja'>{@link oaj.http.annotation.FormData FormData}
 		<li class='ja'>{@link oaj.http.annotation.Query Query}
 		<li class='ja'>{@link oaj.http.annotation.Path Path}
-		<li class='ja'>{@link oaj.http.annotation.HasFormData HasFormData}
-		<li class='ja'>{@link oaj.http.annotation.HasQuery HasQuery}
 		<li class='ja'>{@link oaj.http.annotation.Request Request}
 		<li class='ja'>{@link oaj.http.annotation.Response Response}
 	</ul>
diff --git a/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/09.Response.html b/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/09.Response.html
index 4ad14cf..e02c51e 100644
--- a/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/09.Response.html
+++ b/juneau-doc/src/main/resources/Topics/09.juneau-rest-client/01.RestProxies/09.Response.html
@@ -16,25 +16,18 @@
 {new} @Response
 
 <p>
-	The {@link oaj.http.annotation.Response @Response} annotation can be applied to either <ja>@RemoteMethod</ja>-annotated methods
-	or types returned by <ja>@RemoteMethod</ja>-annotated methods.
+	The {@link oaj.http.annotation.Response @Response} annotation can be applied to types returned by <ja>@RemoteMethod</ja>-annotated methods.
 </p>
 <ul class='doctree'>
 	<li class='ja'>{@link oaj.http.annotation.Response Response}
 	<ul>
 		<li class='jf'>{@link oaj.http.annotation.Response#partParser() partParser} - Override the part parser.
-		<li class='jf'>{@link oaj.http.annotation.Response#schema() schema} - Swagger schema.
 	</ul>
 </ul>
-<h5 class='topic'>On @RemoteMethod-annotated methods</h5>
 <p>
-	The annotation can be used to provide schema information about the response body to provide OpenAPI schema based part parsing and validation.
-</p>
-
-<h5 class='topic'>Response interfaces</h5>
-<p>
-	The <ja>@Response</ja> annotation can also be used to define interfaces for retrieving response parts using a bean-like proxies.
+	The <ja>@Response</ja> annotation can be used to define interfaces for retrieving response parts using a bean-like proxy.
 </p>
+<h5 class='figure'>Example:</h5>
 <p class='bpcode w800'>	
 	<ja>@RemoteResource</ja>
 	<jk>public interface</jk> PetStore {
@@ -76,3 +69,6 @@
 	<li class='ja'>{@link oaj.http.annotation.ResponseHeader ResponseHeader}
 	<li class='ja'>{@link oaj.http.annotation.ResponseStatus ResponseStatus}
 </ul>
+<p>
+	The behavior and functionality of all of the annotations are the same as if they were used on method arguments directly. This means full support for OpenAPI serialization and validation.
+</p>
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index c09314c..d1c53da 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -32,6 +32,7 @@ import org.apache.http.client.config.*;
 import org.apache.http.client.entity.*;
 import org.apache.http.client.methods.*;
 import org.apache.http.client.utils.*;
+import org.apache.http.conn.*;
 import org.apache.http.entity.*;
 import org.apache.http.entity.ContentType;
 import org.apache.http.impl.client.*;
@@ -105,6 +106,7 @@ public final class RestCall extends BeanSession implements Closeable {
 	private HttpPartSchema requestBodySchema, responseBodySchema;
 	private URIBuilder uriBuilder;
 	private NameValuePairs formData;
+	private boolean softClose = false;  // If true, don't consume response and set isClosed flag, but do call listeners.
 
 	/**
 	 * Constructs a REST call with the specified method name.
@@ -1688,9 +1690,7 @@ public final class RestCall extends BeanSession implements Closeable {
 
 		} catch (RestCallException e) {
 			isFailed = true;
-			try {
 			close();
-			} catch (RestCallException e2) { /* Ignore */ }
 			throw e;
 		} catch (Exception e) {
 			isFailed = true;
@@ -1835,7 +1835,26 @@ public final class RestCall extends BeanSession implements Closeable {
 			throw new RestCallException("Response was null");
 		if (response.getEntity() == null)  // HTTP 204 results in no content.
 			return null;
-		InputStream is = response.getEntity().getContent();
+
+		softClose();
+
+		InputStream is = new EofSensorInputStream(response.getEntity().getContent(), new EofSensorWatcher() {
+			@Override
+			public boolean eofDetected(InputStream wrapped) throws IOException {
+				RestCall.this.forceClose();
+				return true;
+			}
+			@Override
+			public boolean streamClosed(InputStream wrapped) throws IOException {
+				RestCall.this.forceClose();
+				return true;
+			}
+			@Override
+			public boolean streamAbort(InputStream wrapped) throws IOException {
+				RestCall.this.forceClose();
+				return true;
+			}
+		});
 
 		if (outputStreams.size() > 0) {
 			ByteArrayInOutStream baios = new ByteArrayInOutStream();
@@ -1864,9 +1883,8 @@ public final class RestCall extends BeanSession implements Closeable {
 			return read(r).toString();
 		} catch (IOException e) {
 			isFailed = true;
-			throw e;
-		} finally {
 			close();
+			throw e;
 		}
 	}
 
@@ -1893,9 +1911,8 @@ public final class RestCall extends BeanSession implements Closeable {
 			return h == null ? null : h.getValue();
 		} catch (IOException e) {
 			isFailed = true;
-			throw e;
-		} finally {
 			close();
+			throw e;
 		}
 	}
 
@@ -1938,9 +1955,8 @@ public final class RestCall extends BeanSession implements Closeable {
 			return partParser.parse(schema, hs, type, args);
 		} catch (IOException e) {
 			isFailed = true;
-			throw e;
-		} finally {
 			close();
+			throw e;
 		}
 	}
 
@@ -2216,8 +2232,10 @@ public final class RestCall extends BeanSession implements Closeable {
 
 			Class<?> ic = type.getInnerClass();
 
-			if (ic.equals(HttpResponse.class))
+			if (ic.equals(HttpResponse.class)) {
+				softClose();
 				return (T)response;
+			}
 			if (ic.equals(Reader.class))
 				return (T)getReader();
 			if (ic.equals(InputStream.class))
@@ -2252,14 +2270,10 @@ public final class RestCall extends BeanSession implements Closeable {
 				getResponseHeader("Content-Type"), parser == null ? null : parser.getMediaTypes()
 			);
 
-		} catch (ParseException e) {
-			isFailed = true;
-			throw e;
-		} catch (IOException e) {
+		} catch (ParseException | IOException e) {
 			isFailed = true;
-			throw e;
-		} finally {
 			close();
+			throw e;
 		}
 	}
 
@@ -2278,6 +2292,7 @@ public final class RestCall extends BeanSession implements Closeable {
 	 */
 	public <T> T getResponse(final ResponseBeanMeta rbm) {
 		try {
+			softClose();
 			Class<T> c = (Class<T>)rbm.getClassMeta().getInnerClass();
 			final RestClient rc = this.client;
 			final HttpPartParser p = ObjectUtils.firstNonNull(partParser, rc.getPartParser());
@@ -2360,14 +2375,20 @@ public final class RestCall extends BeanSession implements Closeable {
 	 */
 	@Override /* Closeable */
 	public void close() throws RestCallException {
-		if (response != null)
+		if (response != null && ! softClose)
 			EntityUtils.consumeQuietly(response.getEntity());
-		isClosed = true;
+		if (! softClose)
+			isClosed = true;
 		if (! isFailed)
 			for (RestCallInterceptor r : interceptors)
 				r.onClose(this);
 	}
 
+	void forceClose() throws RestCallException {
+		softClose = false;
+		close();
+	}
+
 	/**
 	 * Adds a {@link RestCallLogger} to the list of interceptors on this class.
 	 *
@@ -2391,6 +2412,20 @@ public final class RestCall extends BeanSession implements Closeable {
 		return this;
 	}
 
+	/**
+	 * If called, the underlying response stream will not be closed when you call {@link #close()}.
+	 *
+	 * <p>
+	 * This is useful in cases where you want access to that stream after you've already cleaned up this object.
+	 * However, it is your responsibility to close that stream yourself.
+	 *
+	 * @return This object (for method chaining).
+	 */
+	public RestCall softClose() {
+		this.softClose = true;
+		return this;
+	}
+
 	//-----------------------------------------------------------------------------------------------------------------
 	// Utility methods
 	//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 27fded5..d0817cc 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -1118,8 +1118,10 @@ public class RestClient extends BeanContext implements Closeable {
 								if (rt == Boolean.class || rt == boolean.class)
 									return returnCode < 400;
 								throw new RestCallException("Invalid return type on method annotated with @RemoteMethod(returns=HTTP_STATUS).  Only integer and booleans types are valid.");
+							} else if (rmr.getReturnValue() == RemoteReturn.BEAN) {
+								return rc.getResponse(rmr.getResponseBeanMeta());
 							} else {
-								Object v = rc.responseBodySchema(rmr.getSchema()).getResponseBody(method.getGenericReturnType());
+								Object v = rc.getResponseBody(method.getGenericReturnType());
 								if (v == null && method.getReturnType().isPrimitive())
 									v = ClassUtils.getPrimitiveDefault(method.getReturnType());
 								return v;
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodBeanReturn.java
similarity index 58%
copy from juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java
copy to juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodBeanReturn.java
index ebba458..47c7193 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodBeanReturn.java
@@ -12,17 +12,45 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.client.remote;
 
+import static org.apache.juneau.internal.ClassUtils.*;
+
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.httppart.bean.*;
+
 /**
- * Possible values for the {@link RemoteMethod#returns() @RemoteMethod(returns)} annotation.
+ * Represents the metadata about an {@link Response}-annotated return type on a method on a REST proxy class.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul class='doctree'>
+ * 	<li class='link'>{@doc juneau-rest-client.RestProxies}
+ * </ul>
  */
-public enum RemoteReturn {
+public final class RemoteMethodBeanReturn {
+
+	private final ResponseBeanMeta meta;
+	private final HttpPartParser parser;
 
-	/** HTTP response body */
-	BODY,
+	RemoteMethodBeanReturn(Class<? extends HttpPartParser> parser, ResponseBeanMeta meta) {
+		this.parser = newInstance(HttpPartParser.class, parser);
+		this.meta = meta;
+	}
 
-	/** HTTP status code */
-	STATUS,
+	/**
+	 * Returns the parser to use for parsing parts on the response bean.
+	 *
+	 * @return The parser to use for parsing parts on the response bean, or <jk>null</jk> if not defined.
+	 */
+	public HttpPartParser getParser() {
+		return parser;
+	}
 
-	/** Ignore (used for void methods) */
-	NONE;
+	/**
+	 * Returns metadata on the response bean.
+	 *
+	 * @return Metadata about the bean.
+	 */
+	public ResponseBeanMeta getMeta() {
+		return meta;
+	}
 }
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodMeta.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodMeta.java
index 3972da6..cf3bff5 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodMeta.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodMeta.java
@@ -103,9 +103,7 @@ public class RemoteMethodMeta {
 				throw new RemoteMetadataException(m,
 					"Invalid value specified for @RemoteMethod(httpMethod) annotation.  Valid values are [DELTE,GET,POST,PUT].");
 
-			RemoteReturn rv = m.getReturnType() == void.class ? RemoteReturn.NONE : rm == null ? RemoteReturn.BODY : rm.returns();
-
-			methodReturn = new RemoteMethodReturn(m, rv);
+			methodReturn = new RemoteMethodReturn(m);
 
 			fullPath = path.indexOf("://") != -1 ? path : (parentPath.isEmpty() ? urlEncode(path) : (trimSlashes(parentPath) + '/' + urlEncode(path)));
 
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java
index 3a76523..03c2148 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteMethodReturn.java
@@ -13,12 +13,12 @@
 package org.apache.juneau.rest.client.remote;
 
 import static org.apache.juneau.internal.ClassUtils.*;
-import static org.apache.juneau.rest.client.remote.RemoteReturn.*;
 
 import java.lang.reflect.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.httppart.*;
+import org.apache.juneau.httppart.bean.*;
 
 /**
  * Represents the metadata about the returned object of a method on a remote proxy interface.
@@ -30,36 +30,21 @@ import org.apache.juneau.httppart.*;
  */
 public final class RemoteMethodReturn {
 
-	private final HttpPartParser parser;
-	private final HttpPartSchema schema;
 	private final Type returnType;
 	private final RemoteReturn returnValue;
+	private final ResponseBeanMeta meta;
 
-	RemoteMethodReturn(Method m, RemoteReturn returnValue) {
+	RemoteMethodReturn(Method m) {
+		RemoteMethod rm = m.getAnnotation(RemoteMethod.class);
+		RemoteReturn rv = m.getReturnType() == void.class ? RemoteReturn.NONE : rm == null ? RemoteReturn.BODY : rm.returns();
 		this.returnType = m.getGenericReturnType();
 		if (hasAnnotation(Response.class, m)) {
-			this.schema = HttpPartSchema.create(Response.class, m);
-			this.parser = newInstance(HttpPartParser.class, schema.getParser());
+			this.meta = ResponseBeanMeta.create(m, PropertyStore.DEFAULT);
+			rv = RemoteReturn.BEAN;
 		} else {
-			this.schema = null;
-			this.parser = null;
+			this.meta = null;
 		}
-		if (returnValue == null) {
-			if (m.getReturnType() == void.class)
-				returnValue = NONE;
-			else
-				returnValue = BODY;
-		}
-		this.returnValue = returnValue;
-	}
-
-	/**
-	 * Returns the parser to use for parsing this part.
-	 *
-	 * @return The parser to use for parsing this part, or <jk>null</jk> if not specified.
-	 */
-	public HttpPartParser getParser() {
-		return parser;
+		this.returnValue = rv;
 	}
 
 	/**
@@ -67,8 +52,8 @@ public final class RemoteMethodReturn {
 	 *
 	 * @return Schema information about the HTTP part, or <jk>null</jk> if not found.
 	 */
-	public HttpPartSchema getSchema() {
-		return schema;
+	public ResponseBeanMeta getResponseBeanMeta() {
+		return meta;
 	}
 
 	/**
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java
index ebba458..be884f3 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteReturn.java
@@ -23,6 +23,9 @@ public enum RemoteReturn {
 	/** HTTP status code */
 	STATUS,
 
+	/** Response bean */
+	BEAN,
+
 	/** Ignore (used for void methods) */
 	NONE;
 }
diff --git a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RemoteMethodAnnotationTest.java b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RemoteMethodAnnotationTest.java
index 08146eb..6f31559 100644
--- a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RemoteMethodAnnotationTest.java
+++ b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RemoteMethodAnnotationTest.java
@@ -193,7 +193,6 @@ public class RemoteMethodAnnotationTest {
 	public static interface D01 {
 
 		@RemoteMethod(method="POST",path="d01")
-		@Response
 		public String d01a(@Body String foo);
 
 		@RemoteMethod(method="POST",path="d01")
diff --git a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RequestAnnotationTest.java b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RequestAnnotationTest.java
index 629322a..6bbb5eb 100644
--- a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RequestAnnotationTest.java
+++ b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/RequestAnnotationTest.java
@@ -23,6 +23,7 @@ import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.client.*;
 import org.apache.juneau.rest.mock.*;
+import org.apache.juneau.rest.testutils.*;
 import org.junit.*;
 import org.junit.runners.*;
 
@@ -33,16 +34,6 @@ import org.junit.runners.*;
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class RequestAnnotationTest {
 
-	public static class Bean {
-		public int f;
-
-		public static Bean create() {
-			Bean b = new Bean();
-			b.f = 1;
-			return b;
-		}
-	}
-
 	//=================================================================================================================
 	// Basic tests
 	//=================================================================================================================
@@ -279,4 +270,59 @@ public class RequestAnnotationTest {
 	public void d02_annotationOnParameter_nullValue() throws Exception {
 		assertEquals("{body:'',header:null,query:null,path:'{x}'}", dr.post(null));
 	}
+
+	//=================================================================================================================
+	// @Request(partSerializer)
+	//=================================================================================================================
+
+	@RestResource
+	public static class E {
+		@RestMethod(path="/{x}")
+		public String post(@Body Reader r, @Header("X") String h, @Query("x") String q, @Path("x") String p) throws Exception {
+			ObjectMap m = new ObjectMap()
+				.append("body", IOUtils.read(r))
+				.append("header", h)
+				.append("query", q)
+				.append("path", p);
+			return m.toString();
+		}
+	}
+	private static MockRest e = MockRest.create(E.class);
+
+	@Request(partSerializer=XPartSerializer.class)
+	public static class ERequest {
+		@Body
+		public String getBody() {
+			return "foo";
+		}
+		@Header("X")
+		public String getHeader() {
+			return "x";
+		}
+		@Query("x")
+		public String getQuery() {
+			return "x";
+		}
+		@Path("x")
+		public String getPath() {
+			return "x";
+		}
+	}
+
+	@RemoteResource
+	public static interface ER {
+		@RemoteMethod(path="/{x}") String post(ERequest req);
+	}
+
+	private static ER er = RestClient.create().mockHttpConnection(e).build().getRemoteResource(ER.class);
+
+	@Test
+	public void e01_partSerializer() throws Exception {
+		assertEquals("{body:'foo',header:'xxx',query:'xxx',path:'xxx'}", er.post(new ERequest()));
+	}
+	@Test
+	public void a02_partSerializer_nullValue() throws Exception {
+		assertEquals("{body:'',header:null,query:null,path:'{x}'}", er.post(null));
+	}
+
 }
diff --git a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/ResponseAnnotationTest.java b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/ResponseAnnotationTest.java
index 202e269..725bf14 100644
--- a/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/ResponseAnnotationTest.java
+++ b/juneau-rest/juneau-rest-client/src/test/java/org/apache/juneau/rest/client/remote/ResponseAnnotationTest.java
@@ -14,9 +14,11 @@ package org.apache.juneau.rest.client.remote;
 
 import static org.junit.Assert.*;
 
-import org.apache.juneau.*;
+import java.io.*;
+
 import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.http.annotation.Header;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.client.*;
 import org.apache.juneau.rest.mock.*;
@@ -47,22 +49,39 @@ public class ResponseAnnotationTest {
 	@RestResource
 	public static class A {
 		@RestMethod
-		public String postA(@FormData("*") ObjectMap m, @Header("Content-Type") String ct) {
-			assertEquals(ct, "application/x-www-form-urlencoded");
-			return m.toString();
+		public String get(RestResponse res) {
+			res.setHeader("X", "x");
+			res.setStatus(201);
+			return "foo";
 		}
 	}
 	private static MockRest a = MockRest.create(A.class);
 
+	@Response
+	public interface AResponse {
+
+		@ResponseBody
+		Reader getBody();
+
+		@ResponseHeader("X")
+		String getHeader();
+
+		@ResponseStatus
+		int getStatus();
+	}
+
 	@RemoteResource
 	public static interface AR {
-		@RemoteMethod(path="a") String postA01(@FormData("x") int b);
+		@RemoteMethod AResponse get();
 	}
 
 	private static AR ar = RestClient.create().mockHttpConnection(a).build().getRemoteResource(AR.class);
 
 	@Test
-	public void a01_int() throws Exception {
-		assertEquals("{x:'1'}", ar.postA01(1));
+	public void a01_basic() throws Exception {
+		AResponse r = ar.get();
+		assertEquals("foo", IOUtils.read(r.getBody()));
+		assertEquals("x", r.getHeader());
+		assertEquals(201, r.getStatus());
 	}
 }