juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [1/2] incubator-juneau git commit: Add support for @RestMethod proxies.
Date Sat, 25 Mar 2017 17:25:26 GMT
Repository: incubator-juneau
Updated Branches:
  refs/heads/master 1174420cf -> 9db2e03fa


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index ab660b0..161751a 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -53,6 +53,7 @@ import org.apache.juneau.utils.*;
  * 	<li><a class="doclink" href="package-summary.html#RestClient">org.apache.juneau.rest.client &gt; REST client API</a> for more information and code examples.
  * </ul>
  */
+@SuppressWarnings("hiding")
 public final class RestCall {
 
 	private final RestClient client;                       // The client that created this call.
@@ -74,6 +75,9 @@ public final class RestCall {
 	private TeeOutputStream outputStreams = new TeeOutputStream();
 	private boolean isClosed = false;
 	private boolean isFailed = false;
+	private Object input;
+	private Serializer serializer;
+	private Parser parser;
 
 	/**
 	 * Constructs a REST call with the specified method name.
@@ -90,6 +94,8 @@ public final class RestCall {
 		this.retryOn = client.retryOn;
 		this.retries = client.retries;
 		this.retryInterval = client.retryInterval;
+		this.serializer = client.serializer;
+		this.parser = client.parser;
 	}
 
 	/**
@@ -107,17 +113,33 @@ public final class RestCall {
 	 * @throws RestCallException If a retry was attempted, but the entity was not repeatable.
 	 */
 	public RestCall input(final Object input) throws RestCallException {
+		this.input = input;
+		return this;
+	}
 
-		if (! (request instanceof HttpEntityEnclosingRequestBase))
-			throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null);
-
-		HttpEntity entity = (input instanceof HttpEntity) ? (HttpEntity)input : new RestRequestEntity(input, client.serializer);
-
-		((HttpEntityEnclosingRequestBase)request).setEntity(entity);
-
-		if (retries > 1 && ! entity.isRepeatable())
-			throw new RestCallException("Rest call set to retryable, but entity is not repeatable.");
+	/**
+	 * Specifies the serializer to use on this call.
+	 * <p>
+	 * Overrides the serializer specified on the {@link RestClient}.
+	 *
+	 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
+	 * @return This object (for method chaining).
+	 */
+	public RestCall serializer(Serializer serializer) {
+		this.serializer = serializer;
+		return this;
+	}
 
+	/**
+	 * Specifies the parser to use on this call.
+	 * <p>
+	 * Overrides the parser specified on the {@link RestClient}.
+	 *
+	 * @param parser The parser used to parse POJOs from the body of the HTTP response.
+	 * @return This object (for method chaining).
+	 */
+	public RestCall parser(Parser parser) {
+		this.parser = parser;
 		return this;
 	}
 
@@ -520,13 +542,14 @@ public final class RestCall {
 	 * @return This object (for method chaining).
 	 * @throws RestCallException If current entity is not repeatable.
 	 */
-	@SuppressWarnings("hiding")
 	public RestCall retryable(int retries, long interval, RetryOn retryOn) throws RestCallException {
 		if (request instanceof HttpEntityEnclosingRequestBase) {
-		HttpEntity e = ((HttpEntityEnclosingRequestBase)request).getEntity();
-		if (e != null && ! e.isRepeatable())
-			throw new RestCallException("Attempt to make call retryable, but entity is not repeatable.");
-		}
+			if (input != null && input instanceof HttpEntity) {
+				HttpEntity e = (HttpEntity)input;
+				if (e != null && ! e.isRepeatable())
+					throw new RestCallException("Attempt to make call retryable, but entity is not repeatable.");
+				}
+			}
 		this.retries = retries;
 		this.retryInterval = interval;
 		this.retryOn = (retryOn == null ? RetryOn.DEFAULT : retryOn);
@@ -885,6 +908,16 @@ public final class RestCall {
 		isConnected = true;
 
 		try {
+
+			if (input != null) {
+				if (! (request instanceof HttpEntityEnclosingRequestBase))
+					throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null);
+				HttpEntity entity = (input instanceof HttpEntity) ? (HttpEntity)input : new RestRequestEntity(input, getSerializer());
+				((HttpEntityEnclosingRequestBase)request).setEntity(entity);
+				if (retries > 1 && ! entity.isRepeatable())
+					throw new RestCallException("Rest call set to retryable, but entity is not repeatable.");
+			}
+
 			int sc = 0;
 			while (retries > 0) {
 				retries--;
@@ -1028,9 +1061,9 @@ public final class RestCall {
 	 * @throws RestCallException If no parser was defined on the client.
 	 */
 	protected Parser getParser() throws RestCallException {
-		if (client.parser == null)
+		if (parser == null)
 			throw new RestCallException(0, "No parser defined on client", request.getMethod(), request.getURI(), null);
-		return client.parser;
+		return parser;
 	}
 
 	/**
@@ -1040,9 +1073,9 @@ public final class RestCall {
 	 * @throws RestCallException If no serializer was defined on the client.
 	 */
 	protected Serializer getSerializer() throws RestCallException {
-		if (client.serializer == null)
+		if (serializer == null)
 			throw new RestCallException(0, "No serializer defined on client", request.getMethod(), request.getURI(), null);
-		return client.serializer;
+		return serializer;
 	}
 
 	/**
@@ -1114,13 +1147,33 @@ public final class RestCall {
 	}
 
 	/**
-	 * Converts the output from the connection into an object of the specified class using the registered {@link Parser}.
+	 * Same as {@link #getResponse(Type, Type...)} except optimized for a non-parameterized class.
+	 * <p>
+	 * This is the preferred parse method for simple types since you don't need to cast the results.
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a string.</jc>
+	 * 	String s = restClient.doGet(url).getResponse(String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a bean.</jc>
+	 * 	MyBean b = restClient.doGet(url).getResponse(MyBean.<jk>class</jk>);
 	 *
-	 * @param type The class to convert the input to.
-	 * @param <T> The class to convert the input to.
-	 * @return The parsed output.
+	 * 	<jc>// Parse into a bean array.</jc>
+	 * 	MyBean[] ba = restClient.doGet(url).getResponse(MyBean[].<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a linked-list of objects.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map of object keys/values.</jc>
+	 * 	Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>);
+	 * </p>
+	 *
+	 * @param <T> The class type of the object being created.
+	 * See {@link #getResponse(Type, Type...)} for details.
+	 * @param type The object type to create.
+	 * @return The parsed object.
+	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
 	 * @throws IOException If a connection error occurred.
-	 * @throws ParseException If the input contains a syntax error or is malformed for the <code>Content-Type</code> header.
 	 */
 	public <T> T getResponse(Class<T> type) throws IOException, ParseException {
 		BeanContext bc = getParser().getBeanContext();
@@ -1130,28 +1183,48 @@ public final class RestCall {
 	}
 
 	/**
-	 * Same as {@link #getResponse(Class)}, but useful for parsing into maps and collections of specific types.
+	 * Parses HTTP body into the specified object type.
+	 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a linked-list of strings.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a linked-list of beans.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>);
 	 *
-	 * @param type The class to resolve.
-	 * Can be any of the following:
+	 * 	<jc>// Parse into a linked-list of linked-lists of strings.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map of string keys/values.</jc>
+	 * 	Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map containing string keys and values of lists containing beans.</jc>
+	 * 	Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
+	 * </p>
+	 * <p>
+	 * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type.
+	 * <p>
+	 * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types.
+	 * <p>
+	 * The array can be arbitrarily long to indicate arbitrarily complex data structures.
+	 * <p>
+	 * <h5 class='section'>Notes:</h5>
 	 * <ul>
-	 * 	<li>{@link ClassMeta}
-	 * 	<li>{@link Class}
-	 * 	<li>{@link ParameterizedType}
-	 * 	<li>{@link GenericArrayType}
+	 * 	<li>Use the {@link #getResponse(Class)} method instead if you don't need a parameterized map/collection.
 	 * </ul>
+	 *
+	 * @param <T> The class type of the object to create.
+	 * @param type The object type to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
 	 * @param args The type arguments of the class if it's a collection or map.
-	 * Can be any of the following:
-	 * <ul>
-	 * 	<li>{@link ClassMeta}
-	 * 	<li>{@link Class}
-	 * 	<li>{@link ParameterizedType}
-	 * 	<li>{@link GenericArrayType}
-	 * </ul>
-	 * @param <T> The class to convert the input to.
-	 * @return The parsed output.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 * @return The parsed object.
+	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
 	 * @throws IOException If a connection error occurred.
-	 * @throws ParseException If the input contains a syntax error or is malformed for the <code>Content-Type</code> header.
+	 * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections.
 	 */
 	@SuppressWarnings("unchecked")
 	public <T> T getResponse(Type type, Type...args) throws IOException, ParseException {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 9ff546a..6594ced 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -59,7 +59,6 @@ public class RestClient extends CoreObject {
 	private final UrlEncodingSerializer urlEncodingSerializer;  // Used for form posts only.
 	final Parser parser;
 	private final String remoteableServletUri;
-	private final Map<Method,String> remoteableServiceUriMap;
 	private final String rootUrl;
 	private volatile boolean isClosed = false;
 	private final StackTraceElement[] creationStack;
@@ -113,7 +112,6 @@ public class RestClient extends CoreObject {
 		this.headers = Collections.unmodifiableMap(h2);
 		this.interceptors = interceptors.toArray(new RestCallInterceptor[interceptors.size()]);
 		this.remoteableServletUri = remoteableServletUri;
-		this.remoteableServiceUriMap = new ConcurrentHashMap<Method,String>(remoteableServiceUriMap);
 		this.rootUrl = rootUri;
 		this.retryOn = retryOn;
 		this.retries = retries;
@@ -410,29 +408,65 @@ public class RestClient extends CoreObject {
 	 * @throws RuntimeException If the Remotable service URI has not been specified on this
 	 * 	client by calling {@link RestClientBuilder#remoteableServletUri(String)}.
 	 */
-	@SuppressWarnings("unchecked")
 	public <T> T getRemoteableProxy(final Class<T> interfaceClass) {
 		if (remoteableServletUri == null)
 			throw new RuntimeException("Remoteable service URI has not been specified.");
-		return (T)Proxy.newProxyInstance(
-			interfaceClass.getClassLoader(),
-			new Class[] { interfaceClass },
-			new InvocationHandler() {
-				@Override /* InvocationHandler */
-				public Object invoke(Object proxy, Method method, Object[] args) {
-					try {
-						String uri = remoteableServiceUriMap.get(method);
-						if (uri == null) {
-							// Constructing this string each time can be time consuming, so cache it.
-							uri = remoteableServletUri + '/' + interfaceClass.getName() + '/' + ClassUtils.getMethodSignature(method);
-							remoteableServiceUriMap.put(method, uri);
+		return getRemoteableProxy(interfaceClass, remoteableServletUri + '/' + interfaceClass.getName());
+	}
+
+	/**
+	 * Create a new proxy interface for the specified REST PROXY interface.
+	 *
+	 * @param interfaceClass The interface to create a proxy for.
+	 * @param proxyUrl The URL of the REST method annotated with <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>.
+	 * @return The new proxy interface.
+	 */
+	public <T> T getRemoteableProxy(final Class<T> interfaceClass, final Object proxyUrl) {
+		return getRemoteableProxy(interfaceClass, proxyUrl, serializer, parser);
+	}
+
+	/**
+	 * Same as {@link #getRemoteableProxy(Class, Object)} but allows you to override the serializer and parser used.
+	 *
+	 * @param interfaceClass The interface to create a proxy for.
+	 * @param proxyUrl The URL of the REST method annotated with <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>.
+	 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
+	 * @param parser The parser used to parse POJOs from the body of the HTTP response.
+	 * @return The new proxy interface.
+	 */
+	@SuppressWarnings({ "unchecked", "hiding" })
+	public <T> T getRemoteableProxy(final Class<T> interfaceClass, final Object proxyUrl, final Serializer serializer, final Parser parser) {
+		try {
+			return (T)Proxy.newProxyInstance(
+				interfaceClass.getClassLoader(),
+				new Class[] { interfaceClass },
+				new InvocationHandler() {
+
+					final Map<Method,String> uriCache = new ConcurrentHashMap<Method,String>();
+					final String uri = toURI(proxyUrl).toString();
+
+					@Override /* InvocationHandler */
+					public Object invoke(Object proxy, Method method, Object[] args) {
+
+						// Constructing this string each time can be time consuming, so cache it.
+						String u = uriCache.get(method);
+						if (u == null) {
+							try {
+								u = uri + '/' + URLEncoder.encode(ClassUtils.getMethodSignature(method), "utf-8");
+							} catch (UnsupportedEncodingException e) {}
+							uriCache.put(method, u);
+						}
+
+						try {
+							return doPost(u, args).serializer(serializer).parser(parser).getResponse(method.getGenericReturnType());
+						} catch (Exception e) {
+							throw new RuntimeException(e);
 						}
-						return doPost(uri, args).getResponse(method.getReturnType());
-					} catch (Exception e) {
-						throw new RuntimeException(e);
 					}
-				}
-		});
+			});
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 	private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*");

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
index 0075437..39a2f88 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
@@ -1015,6 +1015,20 @@ public class RestClientBuilder extends CoreObjectBuilder {
 		return property(PARSER_fileCharset, value);
 	}
 
+	/**
+	 * When called, <code>No-Trace: true</code> is added to requests.
+	 * <p>
+	 * This gives the opportunity for the servlet to not log errors on invalid requests.
+	 * This is useful for testing purposes when you don't want your log file to show lots
+	 * of errors that are simply the results of testing.
+	 *
+	 * @return This object (for method chaining).
+	 */
+	public RestClientBuilder noTrace() {
+		return header("No-Trace", true);
+	}
+
+
 	@Override /* CoreObjectBuilder */
 	public RestClientBuilder beansRequireDefaultConstructor(boolean value) {
 		super.beansRequireDefaultConstructor(value);
@@ -1342,6 +1356,7 @@ public class RestClientBuilder extends CoreObjectBuilder {
 	@Override /* CoreObjectBuilder */
 	public RestClientBuilder debug(boolean value) {
 		super.debug(value);
+		header("Debug", value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/pom.xml
----------------------------------------------------------------------
diff --git a/juneau-rest-test/pom.xml b/juneau-rest-test/pom.xml
index 0b613dd..7e5cbff 100644
--- a/juneau-rest-test/pom.xml
+++ b/juneau-rest-test/pom.xml
@@ -46,6 +46,7 @@
 		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
+			<scope>compile</scope>
 		</dependency>
 		<dependency>
 			<groupId>javax.ws.rs</groupId>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
new file mode 100644
index 0000000..6b96290
--- /dev/null
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
@@ -0,0 +1,62 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.test;
+
+import java.util.*;
+
+/**
+ * Interface proxy exposed in InterfaceProxyResource and tested in InterfaceProxyTest.
+ */
+public interface InterfaceProxy {
+
+	void returnVoid();
+	int returnInt();
+	Integer returnInteger();
+	float returnFloat();
+	Float returnFloatObject();
+	String returnString();
+	String returnNullString();
+	int[] returnIntArray();
+	String[] returnStringArray();
+	List<Integer> returnIntegerList();
+	List<String> returnStringList();
+	Bean returnBean();
+	Bean[] returnBeanArray();
+	List<Bean> returnBeanList();
+
+	void setNothing();
+	void setInt(int x);
+	void setInteger(Integer x);
+	void setFloat(float x);
+	void setFloatObject(Float x);
+	void setString(String x);
+	void setNullString(String x);
+	void setIntArray(int[] x);
+	void setStringArray(String[] x);
+	void setIntegerList(List<Integer> x);
+	void setStringList(List<String> x);
+	void setBean(Bean x);
+	void setBeanArray(Bean[] x);
+	void setBeanList(List<Bean> x);
+
+	public static class Bean {
+		public int a;
+		public String b;
+
+		Bean init() {
+			this.a = 1;
+			this.b = "foo";
+			return this;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
new file mode 100644
index 0000000..82de1c2
--- /dev/null
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
@@ -0,0 +1,128 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.test;
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.jena.*;
+import org.junit.*;
+
+/**
+ * Tests inteface proxies exposed through <code>@RestMethod(name="PROXY")</code>
+ */
+@RestResource(
+	path="/testInterfaceProxyResource")
+public class InterfaceProxyResource extends RestServletJenaDefault {
+	private static final long serialVersionUID = 1L;
+
+	//====================================================================================================
+	// Test that Q-values are being resolved correctly.
+	//====================================================================================================
+	@RestMethod(name="PROXY", path="/proxy/*")
+	public InterfaceProxy getProxy() {
+		return new InterfaceProxy() {
+			@Override
+			public void returnVoid() {}
+			@Override
+			public Integer returnInteger() { return 1;}
+			@Override
+			public int returnInt() { return 1; }
+			@Override
+			public float returnFloat() { return 1f; }
+			@Override
+			public Float returnFloatObject() { return 1f; }
+			@Override
+			public String returnString() { return "foobar"; }
+			@Override
+			public String returnNullString() { return null; }
+			@Override
+			public int[] returnIntArray() { return new int[]{1,2}; }
+			@Override
+			public String[] returnStringArray() { return new String[]{"foo","bar",null};}
+			@Override
+			public List<Integer> returnIntegerList() { return Arrays.asList(new Integer[]{1,2}); }
+			@Override
+			public List<String> returnStringList() { return Arrays.asList(new String[]{"foo","bar",null}); }
+			@Override
+			public Bean returnBean() { return new Bean().init(); }
+			@Override
+			public Bean[] returnBeanArray() { return new Bean[]{new Bean().init()}; }
+			@Override
+			public List<Bean> returnBeanList() { return Arrays.asList(new Bean().init()); }
+
+			@Override
+			public void setNothing() {
+			}
+			@Override
+			public void setInt(int x) {
+				assertEquals(1, x);
+			}
+			@Override
+			public void setInteger(Integer x) {
+				assertEquals((Integer)1, x);
+			}
+			@Override
+			public void setFloat(float x) {
+				assertTrue(1f == x);
+			}
+			@Override
+			public void setFloatObject(Float x) {
+				assertTrue(1f == x);
+			}
+			@Override
+			public void setString(String x) {
+				assertEquals("foo", x);
+			}
+			@Override
+			public void setNullString(String x) {
+				assertNull(x);
+			}
+			@Override
+			public void setIntArray(int[] x) {
+				assertObjectEquals("[1,2]", x);
+			}
+			@Override
+			public void setStringArray(String[] x) {
+				assertObjectEquals("['foo','bar',null]", x);
+			}
+			@Override
+			public void setIntegerList(List<Integer> x) {
+				assertObjectEquals("[1,2,null]", x);
+			}
+			@Override
+			public void setStringList(List<String> x) {
+				assertObjectEquals("['foo','bar',null]", x);
+			}
+			@Override
+			public void setBean(Bean x) {
+				assertObjectEquals("{a:1,b:'foo'}", x);
+			}
+			@Override
+			public void setBeanArray(Bean[] x) {
+				assertObjectEquals("[{a:1,b:'foo'}]", x);
+			}
+			@Override
+			public void setBeanList(List<Bean> x) {
+				assertObjectEquals("[{a:1,b:'foo'}]", x);
+			}
+		};
+	}
+
+	private static void assertObjectEquals(String e, Object o) {
+		Assert.assertEquals(e, JsonSerializer.DEFAULT_LAX.toString(o));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
index f24c34c..ceeaf48 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
@@ -38,6 +38,7 @@ import org.apache.juneau.rest.labels.*;
 		InheritanceResource.TestParsers.class,
 		InheritanceResource.TestProperties.class,
 		InheritanceResource.TestSerializers.class,
+		InterfaceProxyResource.class,
 		LargePojosResource.class,
 		MessagesResource.Messages2Resource.class,
 		MessagesResource.class,

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
new file mode 100644
index 0000000..4ed272d
--- /dev/null
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
@@ -0,0 +1,225 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.test;
+
+import static org.apache.juneau.rest.test.TestUtils.*;
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.html.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.msgpack.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.test.InterfaceProxy.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.uon.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.xml.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+@RunWith(Parameterized.class)
+public class InterfaceProxyTest extends RestTestcase {
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> getParameters() {
+		return Arrays.asList(new Object[][] {
+			{ /* 0 */ "Json", JsonSerializer.DEFAULT, JsonParser.DEFAULT },
+			{ /* 1 */ "Xml", XmlSerializer.DEFAULT, XmlParser.DEFAULT },
+			{ /* 2 */ "Mixed", JsonSerializer.DEFAULT, XmlParser.DEFAULT },
+			{ /* 3 */ "Html", HtmlSerializer.DEFAULT, HtmlParser.DEFAULT },
+			{ /* 4 */ "MessagePack", MsgPackSerializer.DEFAULT, MsgPackParser.DEFAULT },
+			{ /* 5 */ "UrlEncoding", UrlEncodingSerializer.DEFAULT, UrlEncodingParser.DEFAULT },
+			{ /* 6 */ "Uon", UonSerializer.DEFAULT, UonParser.DEFAULT },
+		});
+	}
+
+	private Serializer serializer;
+	private Parser parser;
+
+	public InterfaceProxyTest(String label, Serializer serializer, Parser parser) {
+		this.serializer = serializer;
+		this.parser = parser;
+	}
+
+	private InterfaceProxy getProxy() {
+		return getClient(serializer, parser).getRemoteableProxy(InterfaceProxy.class, "/testInterfaceProxyResource/proxy");
+	}
+
+	@Test
+	public void returnVoid() {
+		getProxy().returnVoid();
+	}
+
+	@Test
+	public void returnInteger() {
+		assertEquals((Integer)1, getProxy().returnInteger());
+	}
+
+	@Test
+	public void returnInt() {
+		assertEquals(1, getProxy().returnInt());
+	}
+
+	@Test
+	public void returnFloat() {
+		assertTrue(1f == getProxy().returnFloat());
+	}
+
+	@Test
+	public void returnFloatObject() {
+		assertTrue(1f == getProxy().returnFloatObject());
+	}
+
+	@Test
+	public void returnString() {
+		assertEquals("foobar", getProxy().returnString());
+	}
+
+	@Test
+	public void returnNullString() {
+		assertNull(getProxy().returnNullString());
+	}
+
+	@Test
+	public void returnIntArray() {
+		assertObjectEquals("[1,2]", getProxy().returnIntArray());
+	}
+
+	@Test
+	public void returnStringArray() {
+		assertObjectEquals("['foo','bar',null]", getProxy().returnStringArray());
+	}
+
+	@Test
+	public void returnIntegerList() {
+		assertObjectEquals("[1,2]", getProxy().returnIntegerList());
+		assertTrue(getProxy().returnIntegerList().get(0) instanceof Integer);
+	}
+
+	@Test
+	public void returnStringList() {
+		assertObjectEquals("['foo','bar',null]", getProxy().returnStringList());
+		assertTrue(getProxy().returnStringList() instanceof List);
+	}
+
+	@Test
+	public void returnBean() {
+		assertObjectEquals("{a:1,b:'foo'}", getProxy().returnBean());
+		assertClass(InterfaceProxy.Bean.class, getProxy().returnBean());
+	}
+
+	@Test
+	public void returnBeanArray() {
+		assertObjectEquals("[{a:1,b:'foo'}]", getProxy().returnBeanArray());
+		assertClass(InterfaceProxy.Bean.class, getProxy().returnBeanArray()[0]);
+	}
+
+	@Test
+	public void returnBeanList() {
+		assertObjectEquals("[{a:1,b:'foo'}]", getProxy().returnBeanList());
+		assertClass(InterfaceProxy.Bean.class, getProxy().returnBeanList().get(0));
+	}
+
+	@Test
+	public void setNothing() {
+		getProxy().setNothing();
+	}
+
+	@Test
+	public void setInt() {
+		getProxy().setInt(1);
+	}
+
+	@Test
+	public void setWrongInt() {
+		try {
+			getProxy().setInt(2);
+			fail("Exception expected");
+		} catch (Exception e) {
+			// Good.
+		}
+	}
+
+	@Test
+	public void setInteger() {
+		getProxy().setInteger(1);
+	}
+
+	@Test
+	public void setFloat() {
+		getProxy().setFloat(1f);
+	}
+
+	@Test
+	public void setFloatObject() {
+		getProxy().setFloatObject(1f);
+	}
+
+	@Test
+	public void setString() {
+		getProxy().setString("foo");
+	}
+
+	@Test
+	public void setNullString() {
+		getProxy().setNullString(null);
+	}
+
+	@Test
+	public void setNullStringBad() {
+		try {
+			getProxy().setNullString("foo");
+			fail("Exception expected");
+		} catch (Exception e) {
+			// Good.
+		}
+	}
+
+	@Test
+	public void setIntArray() {
+		getProxy().setIntArray(new int[]{1,2});
+	}
+
+	@Test
+	public void setStringArray() {
+		getProxy().setStringArray(new String[]{"foo","bar",null});
+	}
+
+	@Test
+	public void setIntegerList() {
+		getProxy().setIntegerList(Arrays.asList(new Integer[]{1,2,null}));
+	}
+
+	@Test
+	public void setStringList() {
+		getProxy().setStringList(Arrays.asList("foo","bar",null));
+	}
+
+	@Test
+	public void setBean() {
+		getProxy().setBean(new Bean().init());
+	}
+
+	@Test
+	public void setBeanArray() {
+		getProxy().setBeanArray(new Bean[]{new Bean().init()});
+	}
+
+	@Test
+	public void setBeanList() {
+		getProxy().setBeanList(Arrays.asList(new Bean().init()));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
index 70d4a5d..1a23177 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
@@ -12,6 +12,12 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.test;
 
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.client.*;
+import org.apache.juneau.serializer.*;
 import org.junit.*;
 
 /**
@@ -22,15 +28,33 @@ import org.junit.*;
 public class RestTestcase {
 
 	private static boolean microserviceStarted;
+	private static List<RestClient> clients = new ArrayList<RestClient>();
 
 	@BeforeClass
 	public static void setUp() {
 		microserviceStarted = TestMicroservice.startMicroservice();
 	}
 
+	/**
+	 * Creates a REST client against the test microservice using the specified serializer and parser.
+	 * Client is automatically closed on tear-down.
+	 */
+	protected RestClient getClient(Serializer serializer, Parser parser) {
+		RestClient rc = TestMicroservice.client(serializer, parser).build();
+		clients.add(rc);
+		return rc;
+	}
+
 	@AfterClass
 	public static void tearDown() {
 		if (microserviceStarted)
 			TestMicroservice.stopMicroservice();
+		for (RestClient rc : clients) {
+			try {
+				rc.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
index 25a740d..a2f0777 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
@@ -96,7 +96,7 @@ public class TestMicroservice {
 		try {
 			return new RestClientBuilder()
 				.rootUrl(microserviceURI)
-			//	.httpClient(createHttpClient(), true)
+				.noTrace()
 			;
 		} catch (Exception e) {
 			throw new RuntimeException(e);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
index caf9041..102c17e 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
@@ -37,6 +37,13 @@ public class TestUtils {
 	}
 
 	/**
+	 * Assert that the object is an instance of the specified class.
+	 */
+	public static void assertClass(Class<?> c, Object o) {
+		Assert.assertEquals(c, o == null ? null : o.getClass());
+	}
+
+	/**
 	 * Assert that the object equals the specified string after running it through ws.toString().
 	 */
 	public static void assertObjectEquals(String s, Object o, WriterSerializer ws) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
index 2eb975c..0714aca 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
@@ -36,6 +36,7 @@ import org.junit.runners.Suite.*;
 	GroupsTest.class,
 	GzipTest.class,
 	InheritanceTest.class,
+	InterfaceProxyTest.class,
 	JacocoDummyTest.class,
 	LargePojosTest.class,
 	MessagesTest.class,

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
index 5cfa4e5..2fb2560 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
@@ -44,7 +44,7 @@ import org.apache.juneau.urlencoding.*;
  * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
  */
 @SuppressWarnings("hiding")
-final class CallMethod implements Comparable<CallMethod>  {
+class CallMethod implements Comparable<CallMethod>  {
 	private final java.lang.reflect.Method method;
 	private final String httpMethod;
 	private final UrlPathPattern pathPattern;
@@ -60,7 +60,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 	private final UrlEncodingSerializer urlEncodingSerializer;
 	private final ObjectMap properties;
 	private final Map<String,String> defaultRequestHeaders;
-	private final String defaultEncoding;
+	private final String defaultCharset;
 	private final boolean deprecated;
 	private final String description, tags, summary, externalDocs;
 	private final Integer priority;
@@ -86,7 +86,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 		this.urlEncodingSerializer = b.urlEncodingSerializer;
 		this.properties = b.properties;
 		this.defaultRequestHeaders = b.defaultRequestHeaders;
-		this.defaultEncoding = b.defaultEncoding;
+		this.defaultCharset = b.defaultCharset;
 		this.deprecated = b.deprecated;
 		this.description = b.description;
 		this.tags = b.tags;
@@ -98,7 +98,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 	}
 
 	private static class Builder  {
-		private String httpMethod, defaultEncoding, description, tags, summary, externalDocs;
+		private String httpMethod, defaultCharset, description, tags, summary, externalDocs;
 		private UrlPathPattern pathPattern;
 		private CallMethod.MethodParam[] params;
 		private RestGuard[] guards;
@@ -263,7 +263,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 					defaultRequestHeaders.put(h[0], h[1]);
 				}
 
-				defaultEncoding = properties.getString(REST_defaultCharset, context.getDefaultCharset());
+				defaultCharset = properties.getString(REST_defaultCharset, context.getDefaultCharset());
 				String paramFormat = properties.getString(REST_paramFormat, context.getParamFormat());
 				plainParams = paramFormat.equals("PLAIN");
 
@@ -808,8 +808,8 @@ final class CallMethod implements Comparable<CallMethod>  {
 		for (int i = 0; i < pathPattern.getVars().length; i++)
 			req.setPathParameter(pathPattern.getVars()[i], patternVals[i]);
 
-		req.init(method, remainder, createRequestProperties(properties, req), defaultRequestHeaders, defaultEncoding, serializers, parsers, urlEncodingParser, encoders);
-		res.init(req.getProperties(), defaultEncoding, serializers, urlEncodingSerializer, encoders);
+		req.init(method, remainder, createRequestProperties(properties, req), defaultRequestHeaders, defaultCharset, serializers, parsers, urlEncodingParser, encoders);
+		res.init(req.getProperties(), defaultCharset, serializers, urlEncodingSerializer, encoders);
 
 		// Class-level guards
 		for (RestGuard guard : context.getGuards())

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
index 29113f4..89abdc4 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -399,14 +399,58 @@ public final class RestContext extends Context {
 							throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", this.getClass().getName(), method.getName());
 
 						CallMethod sm = new CallMethod(resource, method, this);
-						_javaRestMethods.put(method.getName(), sm);
-
 						String httpMethod = sm.getHttpMethod();
-						if (! routers.containsKey(httpMethod))
-							routers.put(httpMethod, new CallRouter.Builder(httpMethod));
-
-						routers.get(httpMethod).add(sm);
 
+						// PROXY is a special case where a method returns an interface that we
+						// can perform REST calls against.
+						// We override the CallMethod.invoke() method to insert our logic.
+						if ("PROXY".equals(httpMethod)) {
+
+							final ClassMeta<?> interfaceClass = beanContext.getClassMeta(method.getGenericReturnType());
+							sm = new CallMethod(resource, method, this) {
+
+								@Override
+								int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+
+									int rc = super.invoke(pathInfo, req, res);
+									if (rc != SC_OK)
+										return rc;
+
+									final Object o = res.getOutput();
+
+									if ("GET".equals(req.getMethod())) {
+										res.setOutput(ClassUtils.getMethodInfo(interfaceClass.getProxyableMethods().values()));
+										return SC_OK;
+
+									} else if ("POST".equals(req.getMethod())) {
+										if (pathInfo.indexOf('/') != -1)
+											pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1);
+										pathInfo = RestUtils.decode(pathInfo);
+										java.lang.reflect.Method m = interfaceClass.getProxyableMethods().get(pathInfo);
+										if (m != null) {
+											try {
+												// Parse the args and invoke the method.
+												Parser p = req.getParser();
+												Object input = p.isReaderParser() ? req.getReader() : req.getInputStream();
+												res.setOutput(m.invoke(o, p.parseArgs(input, m.getGenericParameterTypes())));
+												return SC_OK;
+											} catch (Exception e) {
+												throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+											}
+										}
+									}
+									return SC_NOT_FOUND;
+								}
+							};
+
+							_javaRestMethods.put(method.getName(), sm);
+							addToRouter(routers, "GET", sm);
+							addToRouter(routers, "POST", sm);
+
+						} else {
+							_javaRestMethods.put(method.getName(), sm);
+							addToRouter(routers, httpMethod, sm);
+						}
 					} catch (RestServletException e) {
 						throw new RestServletException("Problem occurred trying to serialize methods on class {0}, methods={1}", this.getClass().getName(), JsonSerializer.DEFAULT_LAX.serialize(methodsFound)).initCause(e);
 					}
@@ -484,6 +528,12 @@ public final class RestContext extends Context {
 		}
 	}
 
+	private static void addToRouter(Map<String, CallRouter.Builder> routers, String httpMethodName, CallMethod cm) throws RestServletException {
+		if (! routers.containsKey(httpMethodName))
+			routers.put(httpMethodName, new CallRouter.Builder(httpMethodName));
+		routers.get(httpMethodName).add(cm);
+	}
+
 	private static class Builder {
 
 		boolean allowHeaderParams, allowBodyParam, renderResponseStackTraces, useStackTraceHashes;
@@ -1416,5 +1466,4 @@ public final class RestContext extends Context {
 			throw new RestServletException("Exception occurred while constructing class ''{0}''", c).initCause(e);
 		}
 	}
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
index 2b55b47..8a13d2f 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
@@ -148,7 +148,8 @@ public abstract class RestLogger {
 	 * <p>
 	 * Subclasses can override this method to provide their own logic for determining when exceptions are logged.
 	 * <p>
-	 * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string.
+	 * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string
+	 * 	or <code>No-Trace: true</code> is specified in the header.
 	 *
 	 * @param req The HTTP request.
 	 * @param res The HTTP response.
@@ -156,6 +157,8 @@ public abstract class RestLogger {
 	 * @return <jk>true</jk> if exception should be logged.
 	 */
 	protected boolean shouldLog(HttpServletRequest req, HttpServletResponse res, RestException e) {
+		if ("true".equals(req.getHeader("No-Trace")))
+			return false;
 		String q = req.getQueryString();
 		return (q == null ? true : q.indexOf("noTrace=true") == -1);
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
index 98aba93..cd5d42c 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -67,7 +67,8 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	private final RestContext context;
 
 	private final String method;
-	private String pathRemainder, body;
+	private String pathRemainder;
+	private byte[] body;
 	private Method javaMethod;
 	private ObjectMap properties;
 	private SerializerGroup serializerGroup;
@@ -118,14 +119,16 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			method = _method;
 
 			if (context.isAllowBodyParam()) {
-				body = getQueryParameter("body");
-				if (body != null)
+				String b = getQueryParameter("body");
+				if (b != null) {
 					setHeader("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
+					this.body = b.getBytes(IOUtils.UTF8);
+				}
 			}
 
 			defaultServletHeaders = context.getDefaultRequestHeaders();
 
-			debug = "true".equals(getQueryParameter("debug", "false"));
+			debug = "true".equals(getQueryParameter("debug", "false")) || "true".equals(getHeader("Debug", "false"));
 
 			if (debug) {
 				context.getLogger().log(Level.INFO, toString());
@@ -1216,10 +1219,9 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @throws IOException If a problem occurred trying to read from the reader.
 	 */
 	public String getBodyAsString() throws IOException {
-		if (body != null)
-			return body;
-		body = IOUtils.read(getReader()).toString();
-		return body;
+		if (body == null)
+			body = IOUtils.readBytes(getInputStream(), 1024);
+		return new String(body, IOUtils.UTF8);
 	}
 
 	/**
@@ -1247,7 +1249,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 */
 	protected Reader getUnbufferedReader() throws IOException {
 		if (body != null)
-			return new CharSequenceReader(body);
+			return new CharSequenceReader(new String(body, IOUtils.UTF8));
 		return new InputStreamReader(getInputStream(), getCharacterEncoding());
 	}
 
@@ -1263,21 +1265,15 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	@Override /* ServletRequest */
 	public ServletInputStream getInputStream() throws IOException {
 
+		if (body != null)
+			return new ServletInputStream2(body);
+
 		Encoder enc = getEncoder();
 
 		ServletInputStream is = super.getInputStream();
 		if (enc != null) {
 			final InputStream is2 = enc.getInputStream(is);
-			return new ServletInputStream() {
-				@Override /* InputStream */
-				public final int read() throws IOException {
-					return is2.read();
-				}
-				@Override /* InputStream */
-				public final void close() throws IOException {
-					is2.close();
-				}
-			};
+			return new ServletInputStream2(is2);
 		}
 		return is;
 	}
@@ -1893,7 +1889,9 @@ public final class RestRequest extends HttpServletRequestWrapper {
 		for (Map.Entry<String,String> e : defaultServletHeaders.entrySet()) {
 			sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n");
 		}
-		if (method.equals("PUT") || method.equals("POST")) {
+		if (javaMethod == null) {
+			sb.append("***init() not called yet!***\n");
+		} else if (method.equals("PUT") || method.equals("POST")) {
 			sb.append("---Body---\n");
 			try {
 				sb.append(getBodyAsString()).append("\n");
@@ -1985,4 +1983,30 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	void setJavaMethod(Method method) {
 		this.javaMethod = method;
 	}
+
+	/**
+	 * ServletInputStream wrapper around a normal input stream.
+	 */
+	private static class ServletInputStream2 extends ServletInputStream {
+
+		private final InputStream is;
+
+		private ServletInputStream2(InputStream is) {
+			this.is = is;
+		}
+
+		private ServletInputStream2(byte[] b) {
+			this(new ByteArrayInputStream(b));
+		}
+
+		@Override /* InputStream */
+		public final int read() throws IOException {
+			return is.read();
+		}
+
+		@Override /* InputStream */
+		public final void close() throws IOException {
+			is.close();
+		}
+	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
index 4856070..4270a38 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
@@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.encoders.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.*;
@@ -38,13 +39,26 @@ public @interface RestMethod {
 	 * <p>
 	 * Typically <js>"GET"</js>, <js>"PUT"</js>, <js>"POST"</js>, <js>"DELETE"</js>, or <js>"OPTIONS"</js>.
 	 * <p>
-	 * Can also be a non-HTTP-standard name that is passed in through a <code>&amp;method=methodName</code> URL parameter.
-	 * <p>
 	 * Method names are case-insensitive (always folded to upper-case).
 	 * <p>
-	 * If a method name is not specified, then the method name is determined based on the Java method name.<br>
-	 * 	For example, if the method is <code>doPost(...)</code>, then the method name is automatically detected as <js>"POST"</js>.
-
+	 * Besides the standard HTTP method names, the following can also be specified:
+	 * <ul>
+	 * 	<li><js>"*"</js> - Denotes any method.
+	 * 		<br>Use this if you want to capture any HTTP methods in a single Java method.
+	 * 		<br>The {@link Method @Method} annotation and/or {@link RestRequest#getMethod()} method can be used
+	 * 		to distinguish the actual HTTP method name.
+	 * 	<li><js>""</js> - Auto-detect.
+	 * 		<br>The method name is determined based on the Java method name.
+	 * 		<br>For example, if the method is <code>doPost(...)</code>, then the method name is automatically detected as <js>"POST"</js>.
+	 * 	<li><js>"PROXY"</js> - Remote-proxy interface.
+	 * 		<br>This denotes a Java method that returns an object (usually an interface, often annotated with the {@link Remoteable @Remoteable} annotation)
+	 * 		to be used as a remote proxy using <code>RestClient.getRemoteableProxy(Class<T> interfaceClass, String url)</code>.
+	 * 		<br>This allows you to construct client-side interface proxies using REST as a transport medium.
+	 * 		<br>Conceptually, this is simply a fancy <code>POST</code> against the url <js>"/{path}/{javaMethodName}"</js> where the arguments
+	 * 		are marshalled from the client to the server as an HTTP body containing an array of objects,
+	 * 		passed to the method as arguments, and then the resulting object is marshalled back to the client.
+	 * 	<li>Anything else - Overloaded non-HTTP-standard names that are passed in through a <code>&amp;method=methodName</code> URL parameter.
+	 * </ul>
 	 */
 	String name() default "";
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
index 15a96ad..c6bc0f4 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
@@ -115,7 +115,7 @@ public abstract class RemoteableServlet extends RestServletDefault {
 			throw new RestException(SC_NOT_FOUND, "Method not found"); //$NON-NLS-1$
 
 		// Parse the args and invoke the method.
-		ClassMeta<?>[] argTypes = req.getBeanSession().getClassMetas(m.getParameterTypes());
+		ClassMeta<?>[] argTypes = req.getBeanSession().getClassMetas(m.getGenericParameterTypes());
 		Object[] params = p.parseArgs(req.getReader(), argTypes);
 		return m.invoke(service, params);
 	}


Mime
View raw message