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

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


The following commit(s) were added to refs/heads/master by this push:
     new db31683  RestClient tests.
db31683 is described below

commit db316836b999775f94bd56e307a1a10341978f5d
Author: JamesBognar <[email protected]>
AuthorDate: Mon Jun 15 20:11:18 2020 -0400

    RestClient tests.
---
 .../remote/RrpcInterfaceMeta.java}                 |  38 +-
 .../remote/RrpcInterfaceMethodMeta.java}           |   8 +-
 .../org/apache/juneau/remote/RemoteInterface.java  |   5 +
 .../apache/juneau/rest/client2/RemotesTest.java    | 512 ++++++++++++++++++---
 ...erfaceProxyTest.java => RrpcInterfaceTest.java} |   6 +-
 .../org/apache/juneau/rest/client/RestClient.java  |   6 +-
 .../org/apache/juneau/rest/client2/RestClient.java | 259 +++++------
 .../java/org/apache/juneau/rest/RestContext.java   |   7 +-
 .../org/apache/juneau/rest/RestMethodContext.java  |  18 +-
 .../juneau/rest/RestMethodContextBuilder.java      |  12 +
 .../apache/juneau/rest/annotation/RestMethod.java  |   4 +-
 .../org/apache/juneau/rest/remote/RrpcServlet.java |  18 +-
 12 files changed, 652 insertions(+), 241 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterfaceMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMeta.java
similarity index 77%
rename from 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterfaceMeta.java
rename to 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMeta.java
index f2d0f4e..de5fa05 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterfaceMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMeta.java
@@ -10,7 +10,7 @@
 // * "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.remote;
+package org.apache.juneau.http.remote;
 
 import static org.apache.juneau.internal.StringUtils.*;
 import java.lang.reflect.*;
@@ -18,21 +18,22 @@ import java.util.*;
 
 import org.apache.juneau.collections.*;
 import org.apache.juneau.reflect.*;
+import org.apache.juneau.remote.*;
 
 /**
  * Contains the meta-data about a remote proxy REST interface.
  *
  * <p>
- * Captures the information in {@link RemoteInterface @RemoteInterface} 
annotations for caching and reuse.
+ * Captures the information in {@link Remote @Remote} annotations for caching 
and reuse.
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc juneau-rest-server.restRPC}
  * </ul>
  */
-public class RemoteInterfaceMeta {
+public class RrpcInterfaceMeta {
 
-       private final Map<Method,RemoteInterfaceMethod> methods;
-       private final Map<String,RemoteInterfaceMethod> methodsByPath;
+       private final Map<Method,RrpcInterfaceMethodMeta> methods;
+       private final Map<String,RrpcInterfaceMethodMeta> methodsByPath;
        private final String path;
        private final Class<?> c;
 
@@ -40,28 +41,33 @@ public class RemoteInterfaceMeta {
         * Constructor.
         *
         * @param c
-        *      The interface class annotated with a {@link RemoteInterface 
@RemoteInterface} annotation.
+        *      The interface class annotated with a {@link Remote @Remote} 
annotation.
         *      <br>Note that the annotations are optional.
         * @param uri
         *      The absolute URL of the remote REST interface that implements 
this proxy interface.
         *      <br>This is only used on the client side.
         */
-       public RemoteInterfaceMeta(Class<?> c, String uri) {
+       @SuppressWarnings("deprecation")
+       public RrpcInterfaceMeta(Class<?> c, String uri) {
                this.c = c;
                String path = "";
                ClassInfo ci = ClassInfo.of(c);
-               List<RemoteInterface> rr = 
ci.getAnnotations(RemoteInterface.class);
-               for (RemoteInterface r : rr)
+
+               for (RemoteInterface r : 
ci.getAnnotations(RemoteInterface.class))
+                       if (! r.path().isEmpty())
+                               path = trimSlashes(r.path());
+
+               for (Remote r : ci.getAnnotations(Remote.class))
                        if (! r.path().isEmpty())
                                path = trimSlashes(r.path());
 
-               AMap<Method,RemoteInterfaceMethod> methods = AMap.of();
+               AMap<Method,RrpcInterfaceMethodMeta> methods = AMap.of();
                for (MethodInfo m : ci.getPublicMethods())
                        if (m.isPublic())
-                               methods.put(m.inner(), new 
RemoteInterfaceMethod(uri, m.inner()));
+                               methods.put(m.inner(), new 
RrpcInterfaceMethodMeta(uri, m.inner()));
 
-               AMap<String,RemoteInterfaceMethod> methodsByPath = AMap.of();
-               for (RemoteInterfaceMethod rmm : methods.values())
+               AMap<String,RrpcInterfaceMethodMeta> methodsByPath = AMap.of();
+               for (RrpcInterfaceMethodMeta rmm : methods.values())
                        methodsByPath.put(rmm.getPath(), rmm);
 
                this.methods = methods.unmodifiable();
@@ -77,7 +83,7 @@ public class RemoteInterfaceMeta {
         *      <br>The keys never have leading slashes.
         *      <br>The map is never <jk>null</jk>.
         */
-       public Map<String,RemoteInterfaceMethod> getMethodsByPath() {
+       public Map<String,RrpcInterfaceMethodMeta> getMethodsByPath() {
                return methodsByPath;
        }
 
@@ -87,7 +93,7 @@ public class RemoteInterfaceMeta {
         * @param m The method to look up.
         * @return Metadata about the method or <jk>null</jk> if no metadata 
was found.
         */
-       public RemoteInterfaceMethod getMethodMeta(Method m) {
+       public RrpcInterfaceMethodMeta getMethodMeta(Method m) {
                return methods.get(m);
        }
 
@@ -97,7 +103,7 @@ public class RemoteInterfaceMeta {
         * @param p The HTTP path to look for.
         * @return Metadata about the method or <jk>null</jk> if no metadata 
was found.
         */
-       public RemoteInterfaceMethod getMethodMetaByPath(String p) {
+       public RrpcInterfaceMethodMeta getMethodMetaByPath(String p) {
                return methodsByPath.get(p);
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterfaceMethod.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
similarity index 92%
rename from 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterfaceMethod.java
rename to 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
index b609e6a..112d0be 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterfaceMethod.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta.java
@@ -10,7 +10,7 @@
 // * "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.remote;
+package org.apache.juneau.http.remote;
 
 import static org.apache.juneau.internal.StringUtils.*;
 
@@ -22,13 +22,13 @@ import org.apache.juneau.internal.*;
  * Contains the meta-data about a Java method on a remote class.
  *
  * <p>
- * Captures the information in {@link RemoteInterface @RemoteInterface} 
annotations for caching and reuse.
+ * Captures the information in {@link Remote @Remote} annotations for caching 
and reuse.
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc juneau-rest-server.restRPC}
  * </ul>
  */
-public class RemoteInterfaceMethod {
+public class RrpcInterfaceMethodMeta {
 
        private final String url, path;
        private final Method method;
@@ -39,7 +39,7 @@ public class RemoteInterfaceMethod {
         * @param restUrl The absolute URL of the REST interface backing the 
interface proxy.
         * @param m The Java method.
         */
-       public RemoteInterfaceMethod(final String restUrl, Method m) {
+       public RrpcInterfaceMethodMeta(final String restUrl, Method m) {
                this.method = m;
                this.path =  m.getName() + '/' + 
HttpUtils.getMethodArgsSignature(m, true);
                this.url = trimSlashes(restUrl) + '/' + urlEncode(path);
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterface.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterface.java
index 72ff094..26600ea 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterface.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remote/RemoteInterface.java
@@ -17,17 +17,22 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.http.remote.*;
+
 /**
  * Identifies a remote proxy REST interface.
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc juneau-rest-server.restRPC}
  * </ul>
+ *
+ * @deprecated Use {@link Remote}
  */
 @Documented
 @Target({TYPE})
 @Retention(RUNTIME)
 @Inherited
+@Deprecated
 public @interface RemoteInterface {
 
        /**
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RemotesTest.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RemotesTest.java
index a66548a..0b8a215 100644
--- 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RemotesTest.java
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RemotesTest.java
@@ -15,11 +15,15 @@ package org.apache.juneau.rest.client2;
 import static org.junit.Assert.*;
 import static org.junit.runners.MethodSorters.*;
 
+import java.util.concurrent.*;
+
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.client.remote.*;
 import org.apache.juneau.rest.config.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.http.remote.*;
 import org.apache.juneau.http.remote.RemoteMethod;
+import org.apache.juneau.http.remote.RemoteReturn;
 import org.apache.juneau.rest.mock2.*;
 import org.junit.*;
 
@@ -281,129 +285,521 @@ public class RemotesTest {
        // Other tests
        
//=================================================================================================================
 
+       @SuppressWarnings("serial")
+       public static class CException extends Exception {
+               public CException(String msg) {
+                       super(msg);
+               }
+       }
+
        @Rest(path="/C01")
        public static class C01 implements BasicSimpleJsonRest {
-
                @RestMethod
-               public String c01() {
+               public String a() {
                        return "foo";
                }
+       }
+
+       @Remote(path="/")
+       public static interface C01i {
+               @RemoteMethod
+               public String a();
+       }
+
+       @Test
+       public void c01_overriddenRootUrl() throws Exception {
+               C01i x = MockRestClient
+                       .create(C01.class)
+                       .json()
+                       .build()
+                       .getRemote(C01i.class, "http://localhost/C01";);
+
+               assertEquals("foo", x.a());
+       }
+
+       @Test
+       public void c02_rootUriNotSpecified() throws Exception {
+               C01i x = MockRestClient
+                       .create(C01.class)
+                       .json()
+                       .rootUrl("")
+                       .build()
+                       .getRemote(C01i.class);
+
+               try {
+                       x.a();
+                       fail();
+               } catch (RemoteMetadataException e) {
+                       assertEquals("Invalid remote definition found on class 
org.apache.juneau.rest.client2.RemotesTest$C01i. Root URI has not been 
specified.  Cannot construct absolute path to remote resource.", 
e.getLocalizedMessage());
+               }
+       }
+
 
+       @Rest(path="/C03")
+       public static class C03 implements BasicSimpleJsonRest {
                @RestMethod
-               public String c02() {
+               public String a() {
                        return "bar";
                }
-
                @RestMethod
-               public String c03() {
+               public String getB() {
                        return "baz";
                }
+       }
+
+       @Remote(path="/")
+       public static interface C03i {
+               public String a();
+               public String getB();
+       }
 
+       @Test
+       public void c03_methodNotAnnotated() throws Exception {
+               C03i x = MockRestClient
+                       .create(C03.class)
+                       .json()
+                       .build()
+                       .getRemote(C03i.class);
+
+               assertEquals("bar", x.a());
+               assertEquals("baz", x.getB());
+       }
+
+       @Rest(path="/C04")
+       public static class C04 implements BasicSimpleJsonRest {
                @RestMethod
-               public String c04() throws C01Exception {
-                       throw new C01Exception("foo");
+               public String a() throws CException {
+                       throw new CException("foo");
+               }
+       }
+
+       @Remote
+       public static interface C04i {
+               public String a() throws CException;
+       }
+
+       @Test
+       public void c04_rethrownException() throws Exception {
+               C04i x = MockRestClient
+                       .create(C04.class)
+                       .json()
+                       .build()
+                       .getRemote(C04i.class);
+
+               try {
+                       x.a();
+                       fail();
+               } catch (CException e) {
+                       assertEquals("foo", e.getLocalizedMessage());
                }
+       }
 
+       @Rest
+       public static class C05 implements BasicSimpleJsonRest {
                @RestMethod
-               public String c05() throws C02Exception {
-                       throw new C02Exception("foo");
+               public String a() throws CException {
+                       throw new RuntimeException("foo");
                }
        }
 
-       @Remote(path="/")
-       public static interface C01i {
-               @RemoteMethod
-               public String c01();
-               public String c02();
-               public String getC03();
-               public String c04() throws C01Exception;
-               public String c05();
+       @Remote
+       public static interface C05i {
+               public String a() throws CException;
        }
 
-       @SuppressWarnings("serial")
-       public static class C01Exception extends Exception {
-               public C01Exception(String msg) {
-                       super(msg);
+       @Test
+       public void c05_rethrownUndefinedException() throws Exception {
+               C05i x = MockRestClient
+                       .create(C05.class)
+                       .json()
+                       .build()
+                       .getRemote(C05i.class);
+
+               try {
+                       x.a();
+                       fail();
+               } catch (RuntimeException e) {
+                       assertTrue(e.getLocalizedMessage().contains("foo"));
                }
        }
 
-       @SuppressWarnings("serial")
-       public static class C02Exception extends Exception {
-               public C02Exception(String msg) {
-                       super(msg);
+       public static class C06 implements BasicSimpleJsonRest {
+               @RestMethod
+               public String a() throws CException {
+                       throw new CException("foo");
                }
        }
 
+       @Remote
+       public static interface C06i {
+               public Future<String> a() throws CException;
+       }
+
        @Test
-       public void c01_overriddenRootUrl() throws Exception {
-               C01i x = MockRestClient
-                       .create(C01.class)
+       public void c06_rethrownExceptionOnFuture() throws Exception {
+               C06i x = MockRestClient
+                       .create(C06.class)
                        .json()
                        .build()
-                       .getRemote(C01i.class, "http://localhost/C01";);
+                       .getRemote(C06i.class);
+
+               try {
+                       x.a().get();
+                       fail();
+               } catch (ExecutionException e) {
+                       assertEquals("foo", e.getCause().getLocalizedMessage());
+               }
+       }
 
-               assertEquals("foo", x.c01());
+       @Remote
+       public static interface C07i {
+               public CompletableFuture<String> a() throws CException;
        }
 
        @Test
-       public void c02_methodNotAnnotated() throws Exception {
-               C01i x = MockRestClient
-                       .create(C01.class)
+       public void c07_rethrownExceptionOnCompletableFuture() throws Exception 
{
+               C07i x = MockRestClient
+                       .create(C06.class)
                        .json()
                        .build()
-                       .getRemote(C01i.class, "http://localhost/C01";);
+                       .getRemote(C07i.class);
+
+               try {
+                       x.a().get();
+                       fail();
+               } catch (ExecutionException e) {
+                       assertEquals("foo", e.getCause().getLocalizedMessage());
+               }
+       }
 
-               assertEquals("bar", x.c02());
-               assertEquals("baz", x.getC03());
+       @Rest
+       public static class C08 implements BasicSimpleJsonRest {
+               @RestMethod
+               public String a() {
+                       throw new AssertionError("foo");
+               }
+       }
+
+       @Remote
+       public static interface C08i {
+               public Future<String> a() throws AssertionError;
        }
 
        @Test
-       public void c03_rootUriNotSpecified() throws Exception {
-               C01i x = MockRestClient
-                       .create(C01.class)
+       public void c08_rethrownThrowableOnFuture() throws Exception {
+               C08i x = MockRestClient
+                       .create(C08.class)
                        .json()
-                       .rootUrl("")
                        .build()
-                       .getRemote(C01i.class);
+                       .getRemote(C08i.class);
 
                try {
-                       x.c01();
+                       x.a().get();
                        fail();
-               } catch (RemoteMetadataException e) {
-                       assertEquals("Invalid remote definition found on class 
org.apache.juneau.rest.client2.RemotesTest$C01i. Root URI has not been 
specified.  Cannot construct absolute path to remote resource.", 
e.getLocalizedMessage());
+               } catch (ExecutionException e) {
+                       assertEquals("foo", 
e.getCause().getCause().getLocalizedMessage());
                }
        }
 
+       
//=================================================================================================================
+       // Status return type
+       
//=================================================================================================================
+
+       @Rest
+       public static class DA implements BasicSimpleJsonRest {
+               @RestMethod
+               public void getR202(org.apache.juneau.rest.RestResponse res) {
+                       res.setStatus(202);
+               }
+               @RestMethod
+               public void getR400(org.apache.juneau.rest.RestResponse res) {
+                       res.setStatus(400);
+               }
+       }
+
+       @Remote
+       public static interface DAi {
+               @RemoteMethod(path="/r202", returns=RemoteReturn.STATUS)
+               public int a() throws AssertionError;
+               @RemoteMethod(path="/r202", returns=RemoteReturn.STATUS)
+               public Integer b() throws AssertionError;
+               @RemoteMethod(path="/r202", returns=RemoteReturn.STATUS)
+               public boolean c() throws AssertionError;
+               @RemoteMethod(path="/r202", returns=RemoteReturn.STATUS)
+               public Boolean d() throws AssertionError;
+               @RemoteMethod(path="/r202", returns=RemoteReturn.STATUS)
+               public String e() throws AssertionError;
+
+               @RemoteMethod(path="/r400", returns=RemoteReturn.STATUS)
+               public int f() throws AssertionError;
+               @RemoteMethod(path="/r400", returns=RemoteReturn.STATUS)
+               public Integer g() throws AssertionError;
+               @RemoteMethod(path="/r400", returns=RemoteReturn.STATUS)
+               public boolean h() throws AssertionError;
+               @RemoteMethod(path="/r400", returns=RemoteReturn.STATUS)
+               public Boolean i() throws AssertionError;
+               @RemoteMethod(path="/r400", returns=RemoteReturn.STATUS)
+               public String j() throws AssertionError;
+       }
+
        @Test
-       public void c04_rethrownException() throws Exception {
-               C01i x = MockRestClient
-                       .create(C01.class)
+       public void d01_statusReturnType() throws Exception {
+               DAi x = MockRestClient
+                       .create(DA.class)
                        .json()
+                       .ignoreErrors()
                        .build()
-                       .getRemote(C01i.class);
+                       .getRemote(DAi.class);
+
+               assertEquals(202, x.a());
+               assertEquals(202, x.b().intValue());
+               assertEquals(true, x.c());
+               assertEquals(true, x.d());
+               assertEquals(400, x.f());
+               assertEquals(400, x.g().intValue());
+               assertEquals(false, x.h());
+               assertEquals(false, x.i());
 
                try {
-                       x.c04();
+                       x.e();
                        fail();
-               } catch (C01Exception e) {
-                       assertEquals("foo", e.getLocalizedMessage());
+               } catch (Exception e) {
+                       assertEquals("Invalid return type on method annotated 
with @RemoteMethod(returns=RemoteReturn.STATUS).  Only integer and booleans 
types are valid.", e.getCause().getLocalizedMessage());
+               }
+
+               try {
+                       x.j();
+                       fail();
+               } catch (Exception e) {
+                       assertEquals("Invalid return type on method annotated 
with @RemoteMethod(returns=RemoteReturn.STATUS).  Only integer and booleans 
types are valid.", e.getCause().getLocalizedMessage());
                }
        }
 
+       @Rest
+       public static class DB implements BasicSimpleJsonRest {
+               @RestMethod
+               public Integer getA() {
+                       return null;
+               }
+       }
+
+       @Remote
+       public static interface DBi {
+               @RemoteMethod
+               public int a() throws AssertionError;
+       }
 
        @Test
-       public void c05_rethrownUndefinedException() throws Exception {
-               C01i x = MockRestClient
-                       .create(C01.class)
+       public void d02_nullPrimitiveReturn() throws Exception {
+               DBi x = MockRestClient
+                       .create(DB.class)
                        .json()
+                       .ignoreErrors()
                        .build()
-                       .getRemote(C01i.class);
+                       .getRemote(DBi.class);
+
+               assertEquals(0, x.a());
+       }
+
+       @Rest
+       public static class DC implements BasicSimpleJsonRest {
+               @RestMethod
+               public Integer getA() {
+                       return 1;
+               }
+       }
+
+       @Remote
+       public static interface DCi {
+               @RemoteMethod
+               public int a() throws AssertionError;
+       }
 
+       @Test
+       public void d03_primitiveReturn() throws Exception {
+               DCi x = MockRestClient
+                       .create(DC.class)
+                       .json()
+                       .ignoreErrors()
+                       .build()
+                       .getRemote(DCi.class);
+
+               assertEquals(1, x.a());
+       }
+
+       @Rest
+       public static class DD implements BasicSimpleJsonRest {
+               @RestMethod
+               public Integer getA() {
+                       return null;
+               }
+       }
+
+       @Remote
+       public static interface DDi {
+               @RemoteMethod
+               public Integer a() throws AssertionError;
+       }
+
+       @Test
+       public void d04_nullNonPrimitive() throws Exception {
+               DDi x = MockRestClient
+                       .create(DD.class)
+                       .json()
+                       .ignoreErrors()
+                       .build()
+                       .getRemote(DDi.class);
+
+               assertNull(x.a());
+       }
+
+       
//=================================================================================================================
+       // RRPC interfaces
+       
//=================================================================================================================
+
+       public interface E1i {
+               String echo(String body);
+       }
+
+       @Rest
+       public static class E1 implements BasicSimpleJsonRest {
+               @RestMethod(name=HttpMethodName.RRPC)
+               public E1i getProxy() {
+                       return new E1i() {
+                               @Override
+                               public String echo(String body) {
+                                       return body;
+                               }
+                       };
+               }
+       }
+
+       @Test
+       public void e01_rrpcBasic() throws Exception {
+               E1i x = MockRestClient
+                       .create(E1.class)
+                       .rootUrl("http://localhost/proxy";)
+                       .json()
+                       .build()
+                       .getRrpcInterface(E1i.class);
+
+               assertEquals("foo", x.echo("foo"));
+       }
+
+       @Test
+       public void e02_rrpc_noRootPath() throws Exception {
                try {
-                       x.c05();
-                       fail();
+                       MockRestClient
+                               .create(E1.class)
+                               .rootUrl("")
+                               .json()
+                               .build()
+                               .getRrpcInterface(E1i.class);
+               } catch (RemoteMetadataException e) {
+                       assertEquals("Invalid remote definition found on class 
org.apache.juneau.rest.client2.RemotesTest$E1i. Root URI has not been 
specified.  Cannot construct absolute path to remote interface.", 
e.getMessage());
+               }
+       }
+
+       @Remote(path="/proxy")
+       public interface E3i {
+               String echo(String body);
+       }
+
+       @Test
+       public void e03_rrpc_noRestUrl() throws Exception {
+               E3i x = MockRestClient
+                       .create(E1.class)
+                       .rootUrl("http://localhost";)
+                       .json()
+                       .build()
+                       .getRrpcInterface(E3i.class);
+
+               assertEquals("foo", x.echo("foo"));
+       }
+
+       @Remote(path="http://localhost/proxy";)
+       public interface E4i {
+               String echo(String body);
+       }
+
+       @Test
+       public void e04_rrpc_fullPathOnRemotePath() throws Exception {
+               E4i x = MockRestClient
+                       .create(E1.class)
+                       .rootUrl("")
+                       .json()
+                       .build()
+                       .getRrpcInterface(E4i.class);
+
+               assertEquals("foo", x.echo("foo"));
+       }
+
+       public interface E5i {
+               String echo(String body) throws EException;
+       }
+
+       @SuppressWarnings("serial")
+       public static class EException extends Exception {
+               public EException(String msg) {
+                       super(msg);
+               }
+       }
+
+       @Rest
+       public static class E5 implements BasicSimpleJsonRest {
+               @RestMethod(name=HttpMethodName.RRPC)
+               public E5i getProxy() {
+                       return new E5i() {
+                               @Override
+                               public String echo(String body) throws 
EException {
+                                       throw new EException("foobar");
+                               }
+                       };
+               }
+       }
+
+       @Test
+       public void e05_rrpc_rethrownCheckedException() throws Exception {
+               try {
+                       E5i x = MockRestClient
+                               .create(E5.class)
+                               .json()
+                               .build()
+                               .getRrpcInterface(E5i.class, "/proxy");
+
+                       x.echo("foo");
+               } catch (EException e) {
+                       assertEquals("foobar", e.getMessage());
+               }
+       }
+
+       @Rest
+       public static class E6 implements BasicSimpleJsonRest {
+               @RestMethod(name=HttpMethodName.RRPC)
+               public E5i getProxy() {
+                       return new E5i() {
+                               @Override
+                               public String echo(String body) throws 
EException {
+                                       throw new AssertionError("foobar");
+                               }
+                       };
+               }
+       }
+
+       @Test
+       public void e06_rrpc_rethrownUncheckedException() throws Exception {
+               try {
+                       E5i x = MockRestClient
+                               .create(E6.class)
+                               .json()
+                               .build()
+                               .getRrpcInterface(E5i.class, "/proxy");
+
+                       x.echo("foo");
                } catch (RuntimeException e) {
-                       assertTrue(e.getLocalizedMessage().contains("foo"));
+                       assertEquals(RestCallException.class, 
e.getCause().getClass());
+                       
assertTrue(e.getCause().getMessage().contains("foobar"));
                }
        }
+
 }
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/InterfaceProxyTest.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RrpcInterfaceTest.java
similarity index 96%
rename from 
juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/InterfaceProxyTest.java
rename to 
juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RrpcInterfaceTest.java
index 1e4b734..065dac1 100644
--- 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/InterfaceProxyTest.java
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RrpcInterfaceTest.java
@@ -28,7 +28,6 @@ import org.apache.juneau.jena.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.parser.*;
-import org.apache.juneau.remote.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.mock2.*;
@@ -44,7 +43,7 @@ import org.junit.runners.*;
 
 @FixMethodOrder(NAME_ASCENDING)
 @RunWith(Parameterized.class)
-public class InterfaceProxyTest {
+public class RrpcInterfaceTest {
 
        @Parameterized.Parameters
        public static Collection<Object[]> getParameters() {
@@ -60,7 +59,6 @@ public class InterfaceProxyTest {
                });
        }
 
-       @RemoteInterface
        public interface InterfaceProxy {
 
                public static final String SWAP = 
"swap-~!@#$%^&*()_+`-={}[]|:;\"<,>.?/";
@@ -927,7 +925,7 @@ public class InterfaceProxyTest {
 
        private InterfaceProxy proxy;
 
-       public InterfaceProxyTest(String label, Serializer serializer, Parser 
parser) {
+       public RrpcInterfaceTest(String label, Serializer serializer, Parser 
parser) {
                proxy = cache.get(label);
                if (proxy == null) {
                        proxy = 
MockRestClient.create(InterfaceProxyResource.class).serializer(serializer).parser(parser).ignoreErrors(false).build().getRrpcInterface(InterfaceProxy.class,
 "/proxy");
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 5eed9a6..4b1ba6e 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -1383,7 +1383,7 @@ public class RestClient extends BeanContext implements 
Closeable {
        public <T> T getRrpcInterface(final Class<T> interfaceClass, Object 
restUrl, final Serializer serializer, final Parser parser) {
 
                if (restUrl == null) {
-                       RemoteInterfaceMeta rm = new 
RemoteInterfaceMeta(interfaceClass, stringify(restUrl));
+                       RrpcInterfaceMeta rm = new 
RrpcInterfaceMeta(interfaceClass, stringify(restUrl));
                        String path = rm.getPath();
                        if (path.indexOf("://") == -1) {
                                if (rootUrl == null)
@@ -1401,11 +1401,11 @@ public class RestClient extends BeanContext implements 
Closeable {
                                new Class[] { interfaceClass },
                                new InvocationHandler() {
 
-                                       final RemoteInterfaceMeta rm = new 
RemoteInterfaceMeta(interfaceClass, restUrl2);
+                                       final RrpcInterfaceMeta rm = new 
RrpcInterfaceMeta(interfaceClass, restUrl2);
 
                                        @Override /* InvocationHandler */
                                        public Object invoke(Object proxy, 
Method method, Object[] args) throws Throwable {
-                                               RemoteInterfaceMethod rim = 
rm.getMethodMeta(method);
+                                               RrpcInterfaceMethodMeta rim = 
rm.getMethodMeta(method);
 
                                                if (rim == null)
                                                        throw new 
RuntimeException("Method is not exposed as a remote method.");
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
index e74a59c..2b2d7ae 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestClient.java
@@ -65,7 +65,6 @@ import org.apache.juneau.oapi.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.parser.ParseException;
 import org.apache.juneau.reflect.*;
-import org.apache.juneau.remote.*;
 import org.apache.juneau.rest.client.remote.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.urlencoding.*;
@@ -2948,115 +2947,109 @@ public class RestClient extends BeanContext 
implements HttpClient, Closeable, Re
 
                final String restUrl2 = trimSlashes(emptyIfNull(rootUrl));
 
-               try {
-                       return (T)Proxy.newProxyInstance(
-                               interfaceClass.getClassLoader(),
-                               new Class[] { interfaceClass },
-                               new InvocationHandler() {
-
-                                       final RemoteMeta rm = new 
RemoteMeta(interfaceClass);
-
-                                       @Override /* InvocationHandler */
-                                       public Object invoke(Object proxy, 
Method method, Object[] args) throws Throwable {
-                                               RemoteMethodMeta rmm = 
rm.getMethodMeta(method);
-
-                                               String url = rmm.getFullPath();
-                                               if (url.indexOf("://") == -1)
-                                                       url = restUrl2 + '/' + 
url;
-                                               if (url.indexOf("://") == -1)
-                                                       throw new 
RemoteMetadataException(interfaceClass, "Root URI has not been specified.  
Cannot construct absolute path to remote resource.");
-
-                                               String httpMethod = 
rmm.getHttpMethod();
-                                               HttpPartSerializerSession s = 
getPartSerializerSession();
-
-                                               RestRequest rc = 
request(httpMethod, url, hasContent(httpMethod));
-
-                                               rc.serializer(serializer);
-                                               rc.parser(parser);
-
-                                               for (RemoteMethodArg a : 
rmm.getPathArgs())
-                                                       rc.path(a.getName(), 
args[a.getIndex()], a.getSchema(), a.getSerializer(s));
-
-                                               for (RemoteMethodArg a : 
rmm.getQueryArgs())
-                                                       
rc.query(a.isSkipIfEmpty() ? SKIP_IF_EMPTY_FLAGS : DEFAULT_FLAGS, a.getName(), 
args[a.getIndex()], a.getSchema(), a.getSerializer(s));
-
-                                               for (RemoteMethodArg a : 
rmm.getFormDataArgs())
-                                                       
rc.formData(a.isSkipIfEmpty() ? SKIP_IF_EMPTY_FLAGS : DEFAULT_FLAGS, 
a.getName(), args[a.getIndex()], a.getSchema(), a.getSerializer(s));
-
-                                               for (RemoteMethodArg a : 
rmm.getHeaderArgs())
-                                                       
rc.header(a.isSkipIfEmpty() ? SKIP_IF_EMPTY_FLAGS : DEFAULT_FLAGS, a.getName(), 
args[a.getIndex()], a.getSchema(), a.getSerializer(s));
-
-                                               RemoteMethodArg ba = 
rmm.getBodyArg();
-                                               if (ba != null)
-                                                       
rc.body(args[ba.getIndex()], ba.getSchema());
-
-                                               if (rmm.getRequestArgs().length 
> 0) {
-                                                       for 
(RemoteMethodBeanArg rmba : rmm.getRequestArgs()) {
-                                                               RequestBeanMeta 
rbm = rmba.getMeta();
-                                                               Object bean = 
args[rmba.getIndex()];
-                                                               if (bean != 
null) {
-                                                                       for 
(RequestBeanPropertyMeta p : rbm.getProperties()) {
-                                                                               
Object val = p.getGetter().invoke(bean);
-                                                                               
HttpPartType pt = p.getPartType();
-                                                                               
HttpPartSerializerSession ps = p.getSerializer(s);
-                                                                               
String pn = p.getPartName();
-                                                                               
HttpPartSchema schema = p.getSchema();
-                                                                               
EnumSet<AddFlag> flags = schema.isSkipIfEmpty() ? SKIP_IF_EMPTY_FLAGS : 
DEFAULT_FLAGS;
-                                                                               
if (pt == PATH)
-                                                                               
        rc.path(pn, val, schema, p.getSerializer(s));
-                                                                               
else if (val != null) {
-                                                                               
        if (pt == QUERY)
-                                                                               
                rc.query(flags, pn, val, schema, ps);
-                                                                               
        else if (pt == FORMDATA)
-                                                                               
                rc.formData(flags, pn, val, schema, ps);
-                                                                               
        else if (pt == HEADER)
-                                                                               
                rc.header(flags, pn, val, schema, ps);
-                                                                               
        else /* (pt == HttpPartType.BODY) */
-                                                                               
                rc.body(val, schema);
-                                                                               
}
+               return (T)Proxy.newProxyInstance(
+                       interfaceClass.getClassLoader(),
+                       new Class[] { interfaceClass },
+                       new InvocationHandler() {
+
+                               final RemoteMeta rm = new 
RemoteMeta(interfaceClass);
+
+                               @Override /* InvocationHandler */
+                               public Object invoke(Object proxy, Method 
method, Object[] args) throws Throwable {
+                                       RemoteMethodMeta rmm = 
rm.getMethodMeta(method);
+
+                                       String url = rmm.getFullPath();
+                                       if (url.indexOf("://") == -1)
+                                               url = restUrl2 + '/' + url;
+                                       if (url.indexOf("://") == -1)
+                                               throw new 
RemoteMetadataException(interfaceClass, "Root URI has not been specified.  
Cannot construct absolute path to remote resource.");
+
+                                       String httpMethod = rmm.getHttpMethod();
+                                       HttpPartSerializerSession s = 
getPartSerializerSession();
+
+                                       RestRequest rc = request(httpMethod, 
url, hasContent(httpMethod));
+
+                                       rc.serializer(serializer);
+                                       rc.parser(parser);
+
+                                       for (RemoteMethodArg a : 
rmm.getPathArgs())
+                                               rc.path(a.getName(), 
args[a.getIndex()], a.getSchema(), a.getSerializer(s));
+
+                                       for (RemoteMethodArg a : 
rmm.getQueryArgs())
+                                               rc.query(a.isSkipIfEmpty() ? 
SKIP_IF_EMPTY_FLAGS : DEFAULT_FLAGS, a.getName(), args[a.getIndex()], 
a.getSchema(), a.getSerializer(s));
+
+                                       for (RemoteMethodArg a : 
rmm.getFormDataArgs())
+                                               rc.formData(a.isSkipIfEmpty() ? 
SKIP_IF_EMPTY_FLAGS : DEFAULT_FLAGS, a.getName(), args[a.getIndex()], 
a.getSchema(), a.getSerializer(s));
+
+                                       for (RemoteMethodArg a : 
rmm.getHeaderArgs())
+                                               rc.header(a.isSkipIfEmpty() ? 
SKIP_IF_EMPTY_FLAGS : DEFAULT_FLAGS, a.getName(), args[a.getIndex()], 
a.getSchema(), a.getSerializer(s));
+
+                                       RemoteMethodArg ba = rmm.getBodyArg();
+                                       if (ba != null)
+                                               rc.body(args[ba.getIndex()], 
ba.getSchema());
+
+                                       if (rmm.getRequestArgs().length > 0) {
+                                               for (RemoteMethodBeanArg rmba : 
rmm.getRequestArgs()) {
+                                                       RequestBeanMeta rbm = 
rmba.getMeta();
+                                                       Object bean = 
args[rmba.getIndex()];
+                                                       if (bean != null) {
+                                                               for 
(RequestBeanPropertyMeta p : rbm.getProperties()) {
+                                                                       Object 
val = p.getGetter().invoke(bean);
+                                                                       
HttpPartType pt = p.getPartType();
+                                                                       
HttpPartSerializerSession ps = p.getSerializer(s);
+                                                                       String 
pn = p.getPartName();
+                                                                       
HttpPartSchema schema = p.getSchema();
+                                                                       
EnumSet<AddFlag> flags = schema.isSkipIfEmpty() ? SKIP_IF_EMPTY_FLAGS : 
DEFAULT_FLAGS;
+                                                                       if (pt 
== PATH)
+                                                                               
rc.path(pn, val, schema, p.getSerializer(s));
+                                                                       else if 
(val != null) {
+                                                                               
if (pt == QUERY)
+                                                                               
        rc.query(flags, pn, val, schema, ps);
+                                                                               
else if (pt == FORMDATA)
+                                                                               
        rc.formData(flags, pn, val, schema, ps);
+                                                                               
else if (pt == HEADER)
+                                                                               
        rc.header(flags, pn, val, schema, ps);
+                                                                               
else /* (pt == HttpPartType.BODY) */
+                                                                               
        rc.body(val, schema);
                                                                        }
                                                                }
                                                        }
                                                }
+                                       }
 
-                                               RemoteMethodReturn rmr = 
rmm.getReturns();
-                                               if (rmr.isFuture()) {
-                                                       return 
getExecutorService(true).submit(new Callable<Object>() {
-                                                               @Override
-                                                               public Object 
call() throws Exception {
-                                                                       try {
-                                                                               
return executeRemote(interfaceClass, rc, method, rmm);
-                                                                       } catch 
(Exception e) {
-                                                                               
throw e;
-                                                                       } catch 
(Throwable e) {
-                                                                               
throw new RuntimeException(e);
-                                                                       }
+                                       RemoteMethodReturn rmr = 
rmm.getReturns();
+                                       if (rmr.isFuture()) {
+                                               return 
getExecutorService(true).submit(new Callable<Object>() {
+                                                       @Override
+                                                       public Object call() 
throws Exception {
+                                                               try {
+                                                                       return 
executeRemote(interfaceClass, rc, method, rmm);
+                                                               } catch 
(Exception e) {
+                                                                       throw e;
+                                                               } catch 
(Throwable e) {
+                                                                       throw 
new RuntimeException(e);
                                                                }
-                                                       });
-                                               } else if 
(rmr.isCompletableFuture()) {
-                                                       
CompletableFuture<Object> cf = new CompletableFuture<>();
-                                                       
getExecutorService(true).submit(new Callable<Object>() {
-                                                               @Override
-                                                               public Object 
call() throws Exception {
-                                                                       try {
-                                                                               
cf.complete(executeRemote(interfaceClass, rc, method, rmm));
-                                                                               
return null;
-                                                                       } catch 
(Exception e) {
-                                                                               
throw e;
-                                                                       } catch 
(Throwable e) {
-                                                                               
throw new RuntimeException(e);
-                                                                       }
+                                                       }
+                                               });
+                                       } else if (rmr.isCompletableFuture()) {
+                                               CompletableFuture<Object> cf = 
new CompletableFuture<>();
+                                               
getExecutorService(true).submit(new Callable<Object>() {
+                                                       @Override
+                                                       public Object call() 
throws Exception {
+                                                               try {
+                                                                       
cf.complete(executeRemote(interfaceClass, rc, method, rmm));
+                                                               } catch 
(Throwable e) {
+                                                                       
cf.completeExceptionally(e);
                                                                }
-                                                       });
-                                                       return cf;
-                                               }
-
-                                               return 
executeRemote(interfaceClass, rc, method, rmm);
+                                                               return null;
+                                                       }
+                                               });
+                                               return cf;
                                        }
-                       });
-               } catch (Exception e) {
-                       throw new RuntimeException(e);
-               }
+
+                                       return executeRemote(interfaceClass, 
rc, method, rmm);
+                               }
+               });
        }
 
        Object executeRemote(Class<?> interfaceClass, RestRequest rc, Method 
method, RemoteMethodMeta rmm) throws Throwable {
@@ -3076,7 +3069,7 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
                                else if (rt == Boolean.class || rt == 
boolean.class)
                                        ret = returnCode < 400;
                                else
-                                       throw new RestCallException("Invalid 
return type on method annotated with @RemoteMethod(returns=HTTP_STATUS).  Only 
integer and booleans types are valid.");
+                                       throw new RestCallException("Invalid 
return type on method annotated with 
@RemoteMethod(returns=RemoteReturn.STATUS).  Only integer and booleans types 
are valid.");
                        } else if (rmr.getReturnValue() == RemoteReturn.BEAN) {
                                rc.ignoreErrors();
                                res = rc.run();
@@ -3103,7 +3096,7 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
        }
 
        /**
-        * Create a new Remote Interface against a {@link RemoteInterface 
@RemoteInterface}-annotated class.
+        * Create a new proxy interface against an RRPC-style service.
         *
         * <p>
         * Remote interfaces are interfaces exposed on the server side using 
either the <c>RrpcServlet</c>
@@ -3178,10 +3171,10 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
        public <T> T getRrpcInterface(final Class<T> interfaceClass, Object 
restUrl, final Serializer serializer, final Parser parser) {
 
                if (restUrl == null) {
-                       RemoteInterfaceMeta rm = new 
RemoteInterfaceMeta(interfaceClass, stringify(restUrl));
+                       RrpcInterfaceMeta rm = new 
RrpcInterfaceMeta(interfaceClass, "");
                        String path = rm.getPath();
                        if (path.indexOf("://") == -1) {
-                               if (rootUrl == null)
+                               if (isEmpty(rootUrl))
                                        throw new 
RemoteMetadataException(interfaceClass, "Root URI has not been specified.  
Cannot construct absolute path to remote interface.");
                                path = trimSlashes(rootUrl) + '/' + path;
                        }
@@ -3190,43 +3183,34 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
 
                final String restUrl2 = stringify(restUrl);
 
-               try {
-                       return (T)Proxy.newProxyInstance(
-                               interfaceClass.getClassLoader(),
-                               new Class[] { interfaceClass },
-                               new InvocationHandler() {
-
-                                       final RemoteInterfaceMeta rm = new 
RemoteInterfaceMeta(interfaceClass, restUrl2);
+               return (T)Proxy.newProxyInstance(
+                       interfaceClass.getClassLoader(),
+                       new Class[] { interfaceClass },
+                       new InvocationHandler() {
 
-                                       @Override /* InvocationHandler */
-                                       public Object invoke(Object proxy, 
Method method, Object[] args) throws Throwable {
-                                               RemoteInterfaceMethod rim = 
rm.getMethodMeta(method);
+                               final RrpcInterfaceMeta rm = new 
RrpcInterfaceMeta(interfaceClass, restUrl2);
 
-                                               if (rim == null)
-                                                       throw new 
RuntimeException("Method is not exposed as a remote method.");
+                               @Override /* InvocationHandler */
+                               public Object invoke(Object proxy, Method 
method, Object[] args) throws Throwable {
+                                       RrpcInterfaceMethodMeta rim = 
rm.getMethodMeta(method);
 
-                                               String url = rim.getUrl();
+                                       String url = rim.getUrl();
 
-                                               try {
-                                                       RestRequest rc = 
request("POST", url, true).serializer(serializer).body(args);
+                                       try {
+                                               RestRequest rc = 
request("POST", url, true).serializer(serializer).body(args);
 
-                                                       Object v = 
rc.run().getBody().as(method.getGenericReturnType());
-                                                       if (v == null && 
method.getReturnType().isPrimitive())
-                                                               v = 
ClassInfo.of(method.getReturnType()).getPrimitiveDefault();
-                                                       return v;
+                                               Object v = 
rc.run().getBody().as(method.getGenericReturnType());
+                                               if (v == null && 
method.getReturnType().isPrimitive())
+                                                       v = 
ClassInfo.of(method.getReturnType()).getPrimitiveDefault();
+                                               return v;
 
-                                               } catch (RestCallException e) {
-                                                       // Try to throw 
original exception if possible.
-                                                       
ThrowableUtils.throwException(e.getServerExceptionName(), 
e.getServerExceptionMessage(), method.getExceptionTypes());
-                                                       throw new 
RuntimeException(e);
-                                               } catch (Exception e) {
-                                                       throw new 
RuntimeException(e);
-                                               }
+                                       } catch (RestCallException e) {
+                                               // Try to throw original 
exception if possible.
+                                               
ThrowableUtils.throwException(e.getServerExceptionName(), 
e.getServerExceptionMessage(), method.getExceptionTypes());
+                                               throw new RuntimeException(e);
                                        }
-                       });
-               } catch (Exception e) {
-                       throw new RuntimeException(e);
-               }
+                               }
+               });
        }
 
        @Override
@@ -3254,7 +3238,8 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
                logger.log(level, t, msg(msg, args));
                if (logToConsole) {
                        System.err.println(msg(msg, args).get());
-                       t.printStackTrace();
+                       if (t != null)
+                               t.printStackTrace();
                }
        }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 5c1780d..eb6aefc 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -60,10 +60,10 @@ import org.apache.juneau.oapi.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.plaintext.*;
 import org.apache.juneau.reflect.*;
-import org.apache.juneau.remote.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.converters.*;
 import org.apache.juneau.http.exception.*;
+import org.apache.juneau.http.remote.*;
 import org.apache.juneau.rest.reshandlers.*;
 import org.apache.juneau.rest.util.*;
 import org.apache.juneau.rest.vars.*;
@@ -3908,11 +3908,12 @@ public final class RestContext extends BeanContext {
                                                if ("RRPC".equals(httpMethod)) {
 
                                                        final ClassMeta<?> 
interfaceClass = getClassMeta(mi.inner().getGenericReturnType());
-                                                       final 
RemoteInterfaceMeta rim = new 
RemoteInterfaceMeta(interfaceClass.getInnerClass(), null);
+                                                       final RrpcInterfaceMeta 
rim = new RrpcInterfaceMeta(interfaceClass.getInnerClass(), null);
                                                        if 
(rim.getMethodsByPath().isEmpty())
                                                                throw new 
RestException(SC_INTERNAL_SERVER_ERROR, "Method {0} returns an interface {1} 
that doesn't define any remote methods.", mi.getSignature(), 
interfaceClass.getFullName());
 
                                                        
RestMethodContextBuilder smb = new RestMethodContextBuilder(resource, 
mi.inner(), this);
+                                                       smb.dotAll();
                                                        sm = new 
RestMethodContext(smb) {
 
                                                                @Override
@@ -3933,7 +3934,7 @@ public final class RestContext extends BeanContext {
                                                                                
if (pip.indexOf('/') != -1)
                                                                                
        pip = pip.substring(pip.lastIndexOf('/')+1);
                                                                                
pip = urlDecode(pip);
-                                                                               
RemoteInterfaceMethod rmm = rim.getMethodMetaByPath(pip);
+                                                                               
RrpcInterfaceMethodMeta rmm = rim.getMethodMetaByPath(pip);
                                                                                
if (rmm != null) {
                                                                                
        Method m = rmm.getJavaMethod();
                                                                                
        try {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
index 91b28bf..b24497b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
@@ -45,10 +45,10 @@ import org.apache.juneau.internal.HttpUtils;
 import org.apache.juneau.jsonschema.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.reflect.*;
-import org.apache.juneau.remote.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.annotation.Method;
 import org.apache.juneau.http.exception.*;
+import org.apache.juneau.http.remote.*;
 import org.apache.juneau.rest.guards.*;
 import org.apache.juneau.rest.util.*;
 import org.apache.juneau.rest.widget.*;
@@ -318,7 +318,7 @@ public class RestMethodContext extends BeanContext 
implements Comparable<RestMet
         *              <js>"RRPC"</js>
         *              - Remote-proxy interface.
         *              <br>This denotes a Java method that returns an object 
(usually an interface, often annotated with the
-        *              {@link RemoteInterface @RemoteInterface} annotation) to 
be used as a remote proxy using
+        *              {@link Remote @Remote} annotation) to be used as a 
remote proxy using
         *              <c>RestClient.getRemoteInterface(Class&lt;T&gt; 
interfaceClass, String url)</c>.
         *              <br>This allows you to construct client-side interface 
proxies using REST as a transport medium.
         *              <br>Conceptually, this is simply a fancy <c>POST</c> 
against the url <js>"/{path}/{javaMethodName}"</js>
@@ -692,11 +692,19 @@ public class RestMethodContext extends BeanContext 
implements Comparable<RestMet
 
                this.responseMeta = ResponseBeanMeta.create(mi, ps);
 
+               boolean dotAll = b.dotAll;
                List<UrlPathPattern> pathPatterns = new ArrayList<>();
-               for (String p : getArrayProperty(RESTMETHOD_paths, 
String.class))
+               for (String p : getArrayProperty(RESTMETHOD_paths, 
String.class)) {
+                       if (dotAll && ! p.endsWith("/*"))
+                               p += "/*";
                        pathPatterns.add(new UrlPathPattern(p));
-               if (pathPatterns.isEmpty())
-                       pathPatterns.add(new 
UrlPathPattern(HttpUtils.detectHttpPath(method, true)));
+               }
+               if (pathPatterns.isEmpty()) {
+                       String p = HttpUtils.detectHttpPath(method, true);
+                       if (dotAll && ! p.endsWith("/*"))
+                               p += "/*";
+                       pathPatterns.add(new UrlPathPattern(p));
+               }
 
                this.pathPatterns = pathPatterns.toArray(new 
UrlPathPattern[pathPatterns.size()]);
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
index 859897c..b839454 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
@@ -31,6 +31,8 @@ public class RestMethodContextBuilder extends 
BeanContextBuilder {
        RestContext context;
        java.lang.reflect.Method method;
 
+       boolean dotAll;
+
        RestMethodProperties properties;
 
        RestMethodContextBuilder(Object servlet, java.lang.reflect.Method 
method, RestContext context) throws RestServletException {
@@ -68,6 +70,16 @@ public class RestMethodContextBuilder extends 
BeanContextBuilder {
                }
        }
 
+       /**
+        * When enabled, append <js>"/*"</js> to path patterns if not already 
present.
+        *
+        * @return This object (for method chaining).
+        */
+       public RestMethodContextBuilder dotAll() {
+               this.dotAll = true;
+               return this;
+       }
+
        // <FluentSetters>
 
        @Override /* GENERATED - ContextBuilder */
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
index 8fb8f57..ab3d479 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
@@ -20,9 +20,9 @@ import java.lang.annotation.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.rest.*;
-import org.apache.juneau.remote.*;
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.http.remote.*;
 
 /**
  * Identifies a REST Java method on a {@link RestServlet} implementation class.
@@ -470,7 +470,7 @@ public @interface RestMethod {
         *              <js>"RRPC"</js>
         *              - Remote-proxy interface.
         *              <br>This denotes a Java method that returns an object 
(usually an interface, often annotated with the
-        *              {@link RemoteInterface @RemoteInterface} annotation) to 
be used as a remote proxy using
+        *              {@link Remote @Remote} annotation) to be used as a 
remote proxy using
         *              <c>RestClient.getRemoteInterface(Class&lt;T&gt; 
interfaceClass, String url)</c>.
         *              <br>This allows you to construct client-side interface 
proxies using REST as a transport medium.
         *              <br>Conceptually, this is simply a fancy <c>POST</c> 
against the url <js>"/{path}/{javaMethodName}"</js>
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
index 7d57ec2..548be81 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
@@ -29,11 +29,11 @@ import org.apache.juneau.http.annotation.Header;
 import org.apache.juneau.http.annotation.Path;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
-import org.apache.juneau.remote.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.http.exception.*;
 import org.apache.juneau.http.header.*;
+import org.apache.juneau.http.remote.*;
 
 /**
  * Abstract class for defining Remote Interface Services.
@@ -52,7 +52,7 @@ import org.apache.juneau.http.header.*;
 @SuppressWarnings({"serial","javadoc"})
 public abstract class RrpcServlet extends BasicRestServlet {
 
-       private final Map<String,RemoteInterfaceMeta> serviceMap = new 
ConcurrentHashMap<>();
+       private final Map<String,RrpcInterfaceMeta> serviceMap = new 
ConcurrentHashMap<>();
 
        
//-----------------------------------------------------------------------------------------------------------------
        // Abstract methods
@@ -123,7 +123,7 @@ public abstract class RrpcServlet extends BasicRestServlet {
                ) throws NotFound, Exception {
 
                // Find the method.
-               RemoteInterfaceMethod rmm = 
getMethods(javaInterface).get(javaMethod);
+               RrpcInterfaceMethodMeta rmm = 
getMethods(javaInterface).get(javaMethod);
                if (rmm == null)
                        throw new NotFound("Method not found");
 
@@ -193,7 +193,7 @@ public abstract class RrpcServlet extends BasicRestServlet {
                // Find the parser.
                if (p == null)
                        throw new UnsupportedMediaType("Could not find parser 
for media type ''{0}''", contentType);
-               RemoteInterfaceMeta rim = getInterfaceClass(javaInterface);
+               RrpcInterfaceMeta rim = getInterfaceClass(javaInterface);
 
                // Find the service.
                Object service = getServiceMap().get(rim.getJavaClass());
@@ -201,7 +201,7 @@ public abstract class RrpcServlet extends BasicRestServlet {
                        throw new NotFound("Service not found");
 
                // Find the method.
-               RemoteInterfaceMethod rmm = 
getMethods(javaInterface).get(javaMethod);
+               RrpcInterfaceMethodMeta rmm = 
getMethods(javaInterface).get(javaMethod);
                if (rmm == null)
                        throw new NotFound("Method not found");
 
@@ -216,19 +216,19 @@ public abstract class RrpcServlet extends 
BasicRestServlet {
        // Other methods
        
//-----------------------------------------------------------------------------------------------------------------
 
-       private Map<String,RemoteInterfaceMethod> getMethods(String 
javaInterface) throws Exception {
+       private Map<String,RrpcInterfaceMethodMeta> getMethods(String 
javaInterface) throws Exception {
                return getInterfaceClass(javaInterface).getMethodsByPath();
        }
 
        /**
         * Return the <c>Class</c> given it's name if it exists in the services 
map.
         */
-       private RemoteInterfaceMeta getInterfaceClass(String javaInterface) 
throws NotFound, Exception {
-               RemoteInterfaceMeta rm = serviceMap.get(javaInterface);
+       private RrpcInterfaceMeta getInterfaceClass(String javaInterface) 
throws NotFound, Exception {
+               RrpcInterfaceMeta rm = serviceMap.get(javaInterface);
                if (rm == null) {
                        for (Class<?> c : getServiceMap().keySet()) {
                                if (c.getName().equals(javaInterface)) {
-                                       rm = new RemoteInterfaceMeta(c, null);
+                                       rm = new RrpcInterfaceMeta(c, null);
                                        serviceMap.put(javaInterface, rm);
                                        return rm;
                                }

Reply via email to