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);
        }

Reply via email to