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

[juneau] branch master updated: Improve method naming in remoteable proxies.

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 3c2cc7c  Improve method naming in remoteable proxies.
3c2cc7c is described below

commit 3c2cc7ced1b9788b8dfffe346e71a0e6bc80b84e
Author: JamesBognar <ja...@apache.org>
AuthorDate: Thu Aug 16 16:16:40 2018 -0400

    Improve method naming in remoteable proxies.
---
 .../org/apache/juneau/utils/HttpUtilsTest.java     | 170 +++++++++++++++++++++
 .../src/main/java/org/apache/juneau/ClassMeta.java |  48 +-----
 .../java/org/apache/juneau/internal/HttpUtils.java |  92 +++++++++++
 .../org/apache/juneau/remoteable/RemoteMethod.java |  12 +-
 .../org/apache/juneau/remoteable/Remoteable.java   |  63 ++++++--
 .../apache/juneau/remoteable/RemoteableMeta.java   |  80 +++++++++-
 .../juneau/remoteable/RemoteableMethodMeta.java    |  68 ++++++---
 .../juneau/examples/addressbook/IAddressBook.java  |   3 -
 .../org/apache/juneau/rest/client/RestClient.java  |   8 +-
 .../java/org/apache/juneau/rest/RestContext.java   |  12 +-
 .../org/apache/juneau/rest/RestJavaMethod.java     |  33 +---
 .../juneau/rest/remoteable/RemoteableServlet.java  |  51 +++----
 .../apache/juneau/rest/widget/PoweredByJuneau.java |   2 +-
 13 files changed, 485 insertions(+), 157 deletions(-)

diff --git a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/HttpUtilsTest.java b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/HttpUtilsTest.java
new file mode 100644
index 0000000..607c8d1
--- /dev/null
+++ b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/utils/HttpUtilsTest.java
@@ -0,0 +1,170 @@
+// ***************************************************************************************************************************
+// * 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.utils;
+
+import static org.junit.Assert.*;
+
+import static org.apache.juneau.internal.HttpUtils.*;
+
+import org.junit.*;
+
+public class HttpUtilsTest {
+
+	public interface A {
+		void doGet();
+		void doGET();
+		void doPOST();
+		void doFOO();
+		void getFoo();
+		void postFoo();
+		void get();
+		void post();
+		void createFoo();
+	}
+
+	//====================================================================================================
+	// getHttpMethod()
+	//====================================================================================================
+
+	@Test
+	public void testGetHttpMethod_detect_nodefault() throws Exception {
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("doGet"), true, null));
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("doGET"), true, null));
+		assertEquals("POST", detectHttpMethod(A.class.getMethod("doPOST"), true, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("doFOO"), true, null));
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("getFoo"), true, null));
+		assertEquals("POST", detectHttpMethod(A.class.getMethod("postFoo"), true, null));
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("get"), true, null));
+		assertEquals("POST", detectHttpMethod(A.class.getMethod("post"), true, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("createFoo"), true, null));
+	}
+
+	@Test
+	public void testGetHttpMethod_detect_default() throws Exception {
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("doGet"), true, "DELETE"));
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("doGET"), true, "DELETE"));
+		assertEquals("POST", detectHttpMethod(A.class.getMethod("doPOST"), true, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("doFOO"), true, "DELETE"));
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("getFoo"), true, "DELETE"));
+		assertEquals("POST", detectHttpMethod(A.class.getMethod("postFoo"), true, "DELETE"));
+		assertEquals("GET", detectHttpMethod(A.class.getMethod("get"), true, "DELETE"));
+		assertEquals("POST", detectHttpMethod(A.class.getMethod("post"), true, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("createFoo"), true, "DELETE"));
+	}
+
+	@Test
+	public void testGetHttpMethod_nodetect_nodefault() throws Exception {
+		assertEquals(null, detectHttpMethod(A.class.getMethod("doGet"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("doGET"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("doPOST"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("doFOO"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("getFoo"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("postFoo"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("get"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("post"), false, null));
+		assertEquals(null, detectHttpMethod(A.class.getMethod("createFoo"), false, null));
+	}
+
+	@Test
+	public void testGetHttpMethod_nodetect_default() throws Exception {
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("doGet"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("doGET"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("doPOST"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("doFOO"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("getFoo"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("postFoo"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("get"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("post"), false, "DELETE"));
+		assertEquals("DELETE", detectHttpMethod(A.class.getMethod("createFoo"), false, "DELETE"));
+	}
+
+	//====================================================================================================
+	// getRestPath()
+	//====================================================================================================
+
+	public interface B1 {
+		void doGet();
+		void doGET();
+		void doPOST();
+		void doFOO();
+		void getFoo();
+		void postFoo();
+		void get();
+		void post();
+		void createFoo();
+	}
+
+	public interface B2 {
+		void doGet(int x, A y);
+		void doGET(int x, A y);
+		void doPOST(int x, A y);
+		void doFOO(int x, A y);
+		void getFoo(int x, A y);
+		void postFoo(int x, A y);
+		void get(int x, A y);
+		void post(int x, A y);
+		void createFoo(int x, A y);
+	}
+
+	@Test
+	public void testGetHttpPath_noargs_detect() throws Exception {
+		assertEquals("/", detectHttpPath(B1.class.getMethod("doGet"), true));
+		assertEquals("/", detectHttpPath(B1.class.getMethod("doGET"), true));
+		assertEquals("/", detectHttpPath(B1.class.getMethod("doPOST"), true));
+		assertEquals("/doFOO", detectHttpPath(B1.class.getMethod("doFOO"), true));
+		assertEquals("/foo", detectHttpPath(B1.class.getMethod("getFoo"), true));
+		assertEquals("/foo", detectHttpPath(B1.class.getMethod("postFoo"), true));
+		assertEquals("/", detectHttpPath(B1.class.getMethod("get"), true));
+		assertEquals("/", detectHttpPath(B1.class.getMethod("post"), true));
+		assertEquals("/createFoo", detectHttpPath(B1.class.getMethod("createFoo"), true));
+	}
+
+	@Test
+	public void testGetHttpPath_noargs_nodetect() throws Exception {
+		assertEquals("/doGet", detectHttpPath(B1.class.getMethod("doGet"), false));
+		assertEquals("/doGET", detectHttpPath(B1.class.getMethod("doGET"), false));
+		assertEquals("/doPOST", detectHttpPath(B1.class.getMethod("doPOST"), false));
+		assertEquals("/doFOO", detectHttpPath(B1.class.getMethod("doFOO"), false));
+		assertEquals("/getFoo", detectHttpPath(B1.class.getMethod("getFoo"), false));
+		assertEquals("/postFoo", detectHttpPath(B1.class.getMethod("postFoo"), false));
+		assertEquals("/get", detectHttpPath(B1.class.getMethod("get"), false));
+		assertEquals("/post", detectHttpPath(B1.class.getMethod("post"), false));
+		assertEquals("/createFoo", detectHttpPath(B1.class.getMethod("createFoo"), false));
+	}
+
+	@Test
+	public void testGetHttpPath_args_detect() throws Exception {
+		assertEquals("/", detectHttpPath(B2.class.getMethod("doGet",int.class,A.class), true));
+		assertEquals("/", detectHttpPath(B2.class.getMethod("doGET",int.class,A.class), true));
+		assertEquals("/", detectHttpPath(B2.class.getMethod("doPOST",int.class,A.class), true));
+		assertEquals("/doFOO", detectHttpPath(B2.class.getMethod("doFOO",int.class,A.class), true));
+		assertEquals("/foo", detectHttpPath(B2.class.getMethod("getFoo",int.class,A.class), true));
+		assertEquals("/foo", detectHttpPath(B2.class.getMethod("postFoo",int.class,A.class), true));
+		assertEquals("/", detectHttpPath(B2.class.getMethod("get",int.class,A.class), true));
+		assertEquals("/", detectHttpPath(B2.class.getMethod("post",int.class,A.class), true));
+		assertEquals("/createFoo", detectHttpPath(B2.class.getMethod("createFoo",int.class,A.class), true));
+	}
+
+	@Test
+	public void testGetHttpPath_args_nodetect() throws Exception {
+		assertEquals("/doGet", detectHttpPath(B2.class.getMethod("doGet",int.class,A.class), false));
+		assertEquals("/doGET", detectHttpPath(B2.class.getMethod("doGET",int.class,A.class), false));
+		assertEquals("/doPOST", detectHttpPath(B2.class.getMethod("doPOST",int.class,A.class), false));
+		assertEquals("/doFOO", detectHttpPath(B2.class.getMethod("doFOO",int.class,A.class), false));
+		assertEquals("/getFoo", detectHttpPath(B2.class.getMethod("getFoo",int.class,A.class), false));
+		assertEquals("/postFoo", detectHttpPath(B2.class.getMethod("postFoo",int.class,A.class), false));
+		assertEquals("/get", detectHttpPath(B2.class.getMethod("get",int.class,A.class), false));
+		assertEquals("/post", detectHttpPath(B2.class.getMethod("post",int.class,A.class), false));
+		assertEquals("/createFoo", detectHttpPath(B2.class.getMethod("createFoo",int.class,A.class), false));
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index 3aa73d6..9254624 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -31,7 +31,6 @@ import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.parser.*;
-import org.apache.juneau.remoteable.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.transform.*;
 import org.apache.juneau.utils.*;
@@ -93,7 +92,6 @@ public final class ClassMeta<T> implements Type {
 		isMemberClass;                                       // True if this is a non-static member class.
 	private final Object primitiveDefault;                  // Default value for primitive type classes.
 	private final Map<String,Method>
-		remoteableMethods,                                   // Methods annotated with @RemoteMethod.
 		publicMethods;                                       // All public methods, including static methods.
 	private final PojoSwap<?,?>[] childPojoSwaps;           // Any PojoSwaps where the normal type is a subclass of this class.
 	private final ConcurrentHashMap<Class<?>,PojoSwap<?,?>>
@@ -177,7 +175,6 @@ public final class ClassMeta<T> implements Type {
 			this.numberConstructorType = builder.numberConstructorType;
 			this.primitiveDefault = builder.primitiveDefault;
 			this.publicMethods = builder.publicMethods;
-			this.remoteableMethods = builder.remoteableMethods;
 			this.beanFilter = beanFilter;
 			this.pojoSwaps = builder.pojoSwaps.isEmpty() ? null : builder.pojoSwaps.toArray(new PojoSwap[builder.pojoSwaps.size()]);
 			this.builderSwap = builder.builderSwap;
@@ -246,7 +243,6 @@ public final class ClassMeta<T> implements Type {
 		this.isAbstract = mainType.isAbstract;
 		this.isMemberClass = mainType.isMemberClass;
 		this.primitiveDefault = mainType.primitiveDefault;
-		this.remoteableMethods = mainType.remoteableMethods;
 		this.publicMethods = mainType.publicMethods;
 		this.beanContext = mainType.beanContext;
 		this.elementType = elementType;
@@ -299,7 +295,6 @@ public final class ClassMeta<T> implements Type {
 		this.isAbstract = false;
 		this.isMemberClass = false;
 		this.primitiveDefault = null;
-		this.remoteableMethods = null;
 		this.publicMethods = null;
 		this.beanContext = null;
 		this.elementType = null;
@@ -351,8 +346,7 @@ public final class ClassMeta<T> implements Type {
 			numberConstructorType = null;
 		Object primitiveDefault = null;
 		Map<String,Method>
-			publicMethods = new LinkedHashMap<>(),
-			remoteableMethods = new LinkedHashMap<>();
+			publicMethods = new LinkedHashMap<>();
 		ClassMeta<?>
 			keyType = null,
 			valueType = null,
@@ -575,24 +569,6 @@ public final class ClassMeta<T> implements Type {
 				if (isAll(m, PUBLIC, NOT_DEPRECATED))
 					publicMethods.put(getMethodSignature(m), m);
 
-			Map<Class<?>,Remoteable> remoteableMap = getAnnotationsMap(Remoteable.class, c);
-			if (! remoteableMap.isEmpty()) {
-				Map.Entry<Class<?>,Remoteable> e = remoteableMap.entrySet().iterator().next();  // Grab the first one.
-				Class<?> ic = e.getKey();
-				Remoteable r = e.getValue();
-				String methodPaths = r.methodPaths();
-				String expose = r.expose();
-				for (Method m : "DECLARED".equals(expose) ? ic.getDeclaredMethods() : ic.getMethods()) {
-					if (isPublic(m)) {
-						RemoteMethod rm = m.getAnnotation(RemoteMethod.class);
-						if (rm != null || ! "ANNOTATED".equals(expose)) {
-							String path = "NAME".equals(methodPaths) ? m.getName() : getMethodSignature(m);
-							remoteableMethods.put(path, m);
-						}
-					}
-				}
-			}
-
 			if (innerClass != Object.class) {
 				noArgConstructor = (Constructor<T>)findNoArgConstructor(implClass == null ? innerClass : implClass, Visibility.PUBLIC);
 			}
@@ -1484,15 +1460,6 @@ public final class ClassMeta<T> implements Type {
 	}
 
 	/**
-	 * Returns <jk>true</jk> if this class or one of it's methods are annotated with {@link Remoteable @Remotable}.
-	 *
-	 * @return <jk>true</jk> if this class is remoteable.
-	 */
-	public boolean isRemoteable() {
-		return remoteableMethods != null;
-	}
-
-	/**
 	 * Returns <jk>true</jk> if this class is abstract.
 	 *
 	 * @return <jk>true</jk> if this class is abstract.
@@ -1511,19 +1478,6 @@ public final class ClassMeta<T> implements Type {
 	}
 
 	/**
-	 * All methods on this class annotated with {@link Remoteable @Remotable}, or all public methods if class is
-	 * annotated.
-	 *
-	 * <p>
-	 * Keys are method signatures.
-	 *
-	 * @return All remoteable methods on this class.
-	 */
-	public Map<String,Method> getRemoteableMethods() {
-		return remoteableMethods;
-	}
-
-	/**
 	 * All public methods on this class including static methods.
 	 *
 	 * <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/HttpUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/HttpUtils.java
new file mode 100644
index 0000000..22b9c59
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/HttpUtils.java
@@ -0,0 +1,92 @@
+// ***************************************************************************************************************************
+// * 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.internal;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.lang.reflect.*;
+
+/**
+ * Utilities.
+ */
+public class HttpUtils {
+
+	/**
+	 * Given a method name, infers the REST method name.
+	 *
+	 * @param m The Java method.
+	 * @param detectMethod Whether we should auto-detect the HTTP method name from the Java method name.
+	 * @param def The default HTTP method if not detected.
+	 * @return The REST method name, or the default value if not found.
+	 */
+	public static String detectHttpMethod(Method m, boolean detectMethod, String def) {
+		String n = m.getName();
+		if (detectMethod) {
+			if (n.startsWith("do") && n.length() > 2) {
+				String n2 = n.substring(2).toUpperCase();
+				if (isOneOf(n2, "GET","PUT","POST","DELETE","OPTIONS","HEAD","CONNECT","TRACE","PATCH"))
+					return n2;
+			}
+			for (String t : new String[]{"get","put","post","delete","options","head","connect","trace","patch"})
+				if (n.startsWith(t) && (n.length() == t.length() || Character.isUpperCase(n.charAt(t.length()))))
+					return t.toUpperCase();
+		}
+		return def;
+	}
+
+	/**
+	 * Given a Java method, infers the REST path.
+	 *
+	 * @param m The Java method.
+	 * @param detectMethod Whether we should auto-detect the HTTP method name from the Java method name.
+	 * @return The REST path or <jk>null</jk> if not detected.
+	 */
+	public static String detectHttpPath(Method m, boolean detectMethod) {
+		String n = m.getName();
+		if (detectMethod) {
+			if (n.startsWith("do") && n.length() > 2) {
+				String n2 = n.substring(2).toUpperCase();
+				if (isOneOf(n2, "GET","PUT","POST","DELETE","OPTIONS","HEAD","CONNECT","TRACE","PATCH"))
+					return "/";
+			}
+			for (String t : new String[]{"get","put","post","delete","options","head","connect","trace","patch"}) {
+				if (n.startsWith(t) && (n.length() == t.length() || Character.isUpperCase(n.charAt(t.length())))) {
+					return '/' + java.beans.Introspector.decapitalize(n.substring(t.length()));
+				}
+			}
+		}
+		return '/' + n;
+	}
+
+	/**
+	 * Given a Java method, returns the arguments signature.
+	 *
+	 * @param m The Java method.
+	 * @param full Whether fully-qualified names should be used for arguments.
+	 * @return The arguments signature for the specified method.
+	 */
+	public static String getMethodArgsSignature(Method m, boolean full) {
+		StringBuilder sb = new StringBuilder();
+		Class<?>[] pt = m.getParameterTypes();
+		if (pt.length == 0)
+			return "";
+		sb.append('(');
+		for (int i = 0; i < pt.length; i++) {
+			if (i > 0)
+				sb.append(',');
+			sb.append(full ? ClassUtils.getReadableClassName(pt[i]) : ClassUtils.getSimpleName(pt[i]));
+		}
+		sb.append(')');
+		return sb.toString();
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethod.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethod.java
index a8cadfb..3b36de8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethod.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethod.java
@@ -49,10 +49,12 @@ public @interface RemoteMethod {
 	 * The path to the REST service for this Java method relative to the parent proxy interface URL.
 	 *
 	 * <p>
-	 * The default value is the Java method name (e.g. <js>"http://localhost/root-url/org.foo.MyInterface/myMethod"</js>)
-	 * if {@link Remoteable#methodPaths() @Remoteable.methodPaths()} is <js>"NAME"</js>, or the Java method signature
-	 * (e.g. <js>"http://localhost/root-url/org.foo.MyInterface/myMethod(int,boolean,java.lang.String)"</js>) if
-	 * it's <js>"SIGNATURE"</js>.
+	 * If you do not specify a value, then the path is inferred from the Java method name.
+	 *
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul class='doctree'>
+	 * 	<li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies.MethodNames'>Overview &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST Interfaces &gt; Method Names</a>
+	 * </ul>
 	 */
 	String path() default "";
 
@@ -69,7 +71,7 @@ public @interface RemoteMethod {
 	 * <p>
 	 * The default value is <js>"POST"</js>.
 	 */
-	String httpMethod() default "POST";
+	String httpMethod() default "";
 
 	/**
 	 * The value the remoteable method returns.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/Remoteable.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/Remoteable.java
index a1b7afc..6815afe 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/Remoteable.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/Remoteable.java
@@ -57,20 +57,63 @@ public @interface Remoteable {
 	 * 	<li><js>"ALL"</js> - All methods defined on the interface or class are exposed.
 	 * </ul>
 	 */
-	String expose() default "DECLARED";
+	String expose() default "";
 
 	/**
-	 * Defines the methodology to use for the path names of the methods when not explicitly defined via
-	 * {@link RemoteMethod#path() @RemoteMethod.path()}.
+	 * Enable method signature paths.
 	 *
 	 * <p>
-	 * The options are:
-	 * <ul>
-	 * 	<li><js>"NAME"</js> (default) - Use the method name (e.g. "myMethod").
-	 * 	<li><js>"SIGNATURE"</js> - Use the method signature (e.g. "myMethod(int,boolean,java.lang.String,int[][][])").
-	 * </ul>
+	 * When enabled, the HTTP paths for Java methods will default to the full method signature when not specified via {@link RemoteMethod#path() @RemoteMethod(path)}.
+	 *
+	 * <p>
+	 * For example, the HTTP path for the <code>createPerson</code> method below is <js>"createPerson(org.apache.addressbook.CreatePerson)"</js>.
+	 *
+	 * <p class='bcode w800'>
+	 * 	<jk>package</jk> org.apache.addressbook;
+	 *
+	 * 	<ja>@Remoteable</ja>(useMethodSignatures=<jk>true</jk>)
+	 * 	<jk>public interface</jk> IAddressBook {
+	 * 		Person createPerson(CreatePerson cp) <jk>throws</jk> Exception;
+	 * 	}
+	 *
+	 * 	<jk>public class</jk> CreatePerson {...}
+	 * </p>
+	 *
 	 * <p>
-	 * Note that if you use <js>"NAME"</js>, method names must be unique in the interface.
+	 * By default, if you do not specify a <ja>@Remoteable</ja> annotation on your class, the default value for this setting is <jk>true</jk>.
+	 * <br>So the path for the <code>createPerson</code> method shown is the same as above:
+	 *
+	 * <p class='bcode w800'>
+	 * 	<jk>public interface</jk> IAddressBook {
+	 * 		Person createPerson(CreatePerson cp) <jk>throws</jk> Exception;
+	 * 	}
+	 * </p>
+	 *
+	 * <p>
+	 * The path can always be overridden using the {@link RemoteMethod#path() @RemoteMethod(path)} setting like so:
+	 *
+	 * <p class='bcode w800'>
+	 * 	<jk>public interface</jk> IAddressBook {
+	 * 		<ja>@RemoteMethod</ja>(path=<jk>"/people"</jk>)
+	 * 		Person createPerson(CreatePerson cp) <jk>throws</jk> Exception;
+	 * 	}
+	 * </p>
+	 *
+	 * <p>
+	 * If this setting is NOT enabled, then we infer the HTTP method and path from the Java method name.
+	 * <br>In the example below, the HTTP method is detected as <js>"POST"</js> and the path is <js>"/person"</js>.
+	 *
+	 * <p class='bcode w800'>
+	 * 	<ja>@Remoteable</ja>
+	 * 	<jk>public interface</jk> IAddressBook {
+	 * 		Person postPerson(CreatePerson cp) <jk>throws</jk> Exception;
+	 * 	}
+	 * </p>
+	 *
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul class='doctree'>
+	 * 	<li class='link'><a class='doclink' href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies.MethodNames'>Overview &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST Interfaces &gt; Method Names</a>
+	 * </ul>
 	 */
-	String methodPaths() default "NAME";
+	boolean useMethodSignatures() default false;
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMeta.java
index 89969fa..770a600 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMeta.java
@@ -34,6 +34,9 @@ import java.util.*;
 public class RemoteableMeta {
 
 	private final Map<Method,RemoteableMethodMeta> methods;
+	private final Map<String,RemoteableMethodMeta> methodsByPath;
+	private final String path;
+	private final Class<?> c;
 
 	/**
 	 * Constructor.
@@ -42,31 +45,94 @@ public class RemoteableMeta {
 	 * @param restUrl The absolute URL of the remote REST interface that implements this proxy interface.
 	 */
 	public RemoteableMeta(Class<?> c, String restUrl) {
-		Remoteable r = getAnnotation(Remoteable.class, c);
+		this.c = c;
+		String expose = "DECLARED";
+		boolean useSigs = false;
+		String path = "";
+		List<Remoteable> rr = getAnnotationsParentFirst(Remoteable.class, c);
+		if (rr.isEmpty()) {
+			useSigs = true;
+		} else for (Remoteable r : rr) {
+			if (! r.expose().isEmpty())
+				expose = r.expose();
+			if (! r.path().isEmpty())
+				path = trimSlashes(r.path());
+			useSigs |= r.useMethodSignatures();
+		}
 
-		String expose = r == null ? "DECLARED" : r.expose();
 		if (! isOneOf(expose, "ALL", "DECLARED", "ANNOTATED"))
 			throw new RemoteableMetadataException(c, "Invalid value specified for ''expose'' annotation.  Valid values are [ALL,ANNOTATED,DECLARED].");
 
-		Map<Method,RemoteableMethodMeta> _methods = new LinkedHashMap<>();
+		Map<Method,RemoteableMethodMeta> methods = new LinkedHashMap<>();
+		Map<String,RemoteableMethodMeta> methodsByPath = new LinkedHashMap<>();
 		for (Method m : expose.equals("DECLARED") ? c.getDeclaredMethods() : c.getMethods()) {
 			if (isPublic(m)) {
 				RemoteMethod rm = c.getAnnotation(RemoteMethod.class);
-				if (rm != null || ! expose.equals("ANNOTATED"))
-					_methods.put(m, new RemoteableMethodMeta(restUrl, m));
+				if (rm != null || ! expose.equals("ANNOTATED")) {
+					RemoteableMethodMeta rmm = new RemoteableMethodMeta(restUrl, m, useSigs);
+					methods.put(m, rmm);
+					methodsByPath.put(rmm.getPath(), rmm);
+				}
 			}
 		}
 
-		this.methods = unmodifiableMap(_methods);
+		this.methods = unmodifiableMap(methods);
+		this.methodsByPath = unmodifiableMap(methodsByPath);
+		this.path = path;
+	}
+
+	/**
+	 * Returns a map of all methods on this interface proxy keyed by HTTP path.
+	 *
+	 * @return
+	 * 	A map of all methods on this remoteable interface keyed by HTTP path.
+	 * 	<br>The keys never have leading slashes.
+	 * 	<br>The map is never <jk>null</jk>.
+	 */
+	public Map<String,RemoteableMethodMeta> getMethodsByPath() {
+		return methodsByPath;
 	}
 
 	/**
 	 * Returns the metadata about the specified method on this interface proxy.
 	 *
 	 * @param m The method to look up.
-	 * @return Metadata about the method, or <jk>null</jk> if no metadata was found.
+	 * @return Metadata about the method or <jk>null</jk> if no metadata was found.
 	 */
 	public RemoteableMethodMeta getMethodMeta(Method m) {
 		return methods.get(m);
 	}
+
+	/**
+	 * Returns the metadata about the specified method on this interface proxy by the path defined on the method.
+	 *
+	 * @param p The HTTP path to look for.
+	 * @return Metadata about the method or <jk>null</jk> if no metadata was found.
+	 */
+	public RemoteableMethodMeta getMethodMetaByPath(String p) {
+		return methodsByPath.get(p);
+	}
+
+	/**
+	 * Returns the Java class of this interface.
+	 *
+	 * @return
+	 * 	The Java class of this interface.
+	 * 	<br>Never <jk>null</jk>.
+	 */
+	public Class<?> getJavaClass() {
+		return c;
+	}
+
+	/**
+	 * Returns the HTTP path of this interface.
+	 *
+	 * @return
+	 * 	The HTTP path of this interface.
+	 * 	<br>Never <jk>null</jk>.
+	 * 	<br>Never has leading or trailing slashes.
+	 */
+	public String getPath() {
+		return path;
+	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
index e3ed3df..96bd0d4 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
@@ -12,7 +12,6 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.remoteable;
 
-import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.httppart.HttpPartType.*;
 
@@ -23,6 +22,7 @@ import org.apache.juneau.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.httppart.bean.*;
+import org.apache.juneau.internal.*;
 
 /**
  * Contains the meta-data about a Java method on a remoteable interface.
@@ -38,21 +38,25 @@ import org.apache.juneau.httppart.bean.*;
 public class RemoteableMethodMeta {
 
 	private final String httpMethod;
-	private final String url;
+	private final String url, path;
 	private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs, otherArgs;
 	private final RemoteMethodBeanArg[] requestArgs;
 	private final RemoteMethodArg bodyArg;
 	private final RemoteMethodReturn methodReturn;
+	private final Method method;
 
 	/**
 	 * Constructor.
 	 *
 	 * @param restUrl The absolute URL of the REST interface backing the interface proxy.
 	 * @param m The Java method.
+	 * @param useMethodSignatures If <jk>true</jk> then the default path for the method should be the full method signature.
 	 */
-	public RemoteableMethodMeta(final String restUrl, Method m) {
-		Builder b = new Builder(restUrl, m);
+	public RemoteableMethodMeta(final String restUrl, Method m, boolean useMethodSignatures) {
+		Builder b = new Builder(restUrl, m, useMethodSignatures);
+		this.method = m;
 		this.httpMethod = b.httpMethod;
+		this.path = b.path;
 		this.url = b.url;
 		this.pathArgs = b.pathArgs.toArray(new RemoteMethodArg[b.pathArgs.size()]);
 		this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]);
@@ -65,7 +69,7 @@ public class RemoteableMethodMeta {
 	}
 
 	private static final class Builder {
-		String httpMethod, url;
+		String httpMethod, url, path;
 		List<RemoteMethodArg>
 			pathArgs = new LinkedList<>(),
 			queryArgs = new LinkedList<>(),
@@ -77,30 +81,33 @@ public class RemoteableMethodMeta {
 		RemoteMethodArg bodyArg;
 		RemoteMethodReturn methodReturn;
 
-		Builder(String restUrl, Method m) {
-			Remoteable r = m.getDeclaringClass().getAnnotation(Remoteable.class);
+		Builder(String restUrl, Method m, boolean useMethodSignatures) {
+
 			RemoteMethod rm = m.getAnnotation(RemoteMethod.class);
 
-			httpMethod = rm == null ? "POST" : rm.httpMethod();
-			if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT"))
-				throw new RemoteableMetadataException(m,
-					"Invalid value specified for @RemoteMethod.httpMethod() annotation.  Valid values are [DELTE,GET,POST,PUT].");
+			httpMethod = rm == null ? "" : rm.httpMethod();
+			path = rm == null ? "" : rm.path();
+
+			if (path.isEmpty()) {
+				path = HttpUtils.detectHttpPath(m, ! useMethodSignatures);
+				if (useMethodSignatures)
+					path += HttpUtils.getMethodArgsSignature(m, true);
+			}
+			if (httpMethod.isEmpty())
+				httpMethod = HttpUtils.detectHttpMethod(m, ! useMethodSignatures, "POST");
 
-			String path = rm == null || rm.path().isEmpty() ? null : rm.path();
-			String methodPaths = r == null ? "NAME" : r.methodPaths();
+			if (path.startsWith("/"))
+				path = path.substring(1);
 
-			if (! isOneOf(methodPaths, "NAME", "SIGNATURE"))
+			if (! isOneOf(httpMethod, "DELETE", "GET", "POST", "PUT", "OPTIONS", "HEAD", "CONNECT", "TRACE", "PATCH"))
 				throw new RemoteableMetadataException(m,
-					"Invalid value specified for @Remoteable.methodPaths() annotation.  Valid values are [NAME,SIGNATURE].");
+					"Invalid value specified for @RemoteMethod.httpMethod() annotation.  Valid values are [DELTE,GET,POST,PUT].");
 
 			ReturnValue rv = m.getReturnType() == void.class ? ReturnValue.NONE : rm == null ? ReturnValue.BODY : rm.returns();
 
 			methodReturn = new RemoteMethodReturn(m, rv);
 
-			url =
-				trimSlashes(restUrl)
-				+ '/'
-				+ (path != null ? trimSlashes(path) : urlEncode("NAME".equals(methodPaths) ? m.getName() : getMethodSignature(m)));
+			url = trimSlashes(restUrl) + '/' + urlEncode(path);
 
 			for (int i = 0; i < m.getParameterTypes().length; i++) {
 				RemoteMethodArg rma = RemoteMethodArg.create(m, i);
@@ -222,4 +229,27 @@ public class RemoteableMethodMeta {
 	public RemoteMethodReturn getReturns() {
 		return methodReturn;
 	}
+
+	/**
+	 * Returns the HTTP path of this method.
+	 *
+	 * @return
+	 * 	The HTTP path of this method relative to the parent interface.
+	 * 	<br>Never <jk>null</jk>.
+	 * 	<br>Never has leading or trailing slashes.
+	 */
+	public String getPath() {
+		return path;
+	}
+
+	/**
+	 * Returns the underlying Java method that this metadata is about.
+	 *
+	 * @return
+	 * 	The underlying Java method that this metadata is about.
+	 * 	<br>Never <jk>null</jk>.
+	 */
+	public Method getJavaMethod() {
+		return method;
+	}
 }
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
index 80dba47..4f68025 100755
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
@@ -14,12 +14,9 @@ package org.apache.juneau.examples.addressbook;
 
 import java.util.*;
 
-import org.apache.juneau.remoteable.*;
-
 /**
  * Interface used to help illustrate proxy interfaces.
  */
-@Remoteable
 public interface IAddressBook {
 
 	/** Initialize this address book with preset entries */
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 d102e32..09a5944 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
@@ -12,7 +12,6 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.client;
 
-import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.httppart.HttpPartType.*;
 import static org.apache.juneau.remoteable.ReturnValue.*;
@@ -1020,9 +1019,8 @@ public class RestClient extends BeanContext implements Closeable {
 	public <T> T getRemoteableProxy(final Class<T> interfaceClass, Object restUrl, final Serializer serializer, final Parser parser) {
 
 		if (restUrl == null) {
-			Remoteable r = getAnnotation(Remoteable.class, interfaceClass);
-
-			String path = r == null ? "" : trimSlashes(r.path());
+			RemoteableMeta rm = new RemoteableMeta(interfaceClass, asString(restUrl));
+			String path = rm.getPath();
 			if (path.indexOf("://") == -1) {
 				if (rootUrl == null)
 					throw new RemoteableMetadataException(interfaceClass, "Root URI has not been specified.  Cannot construct absolute path to remoteable proxy.");
@@ -1085,7 +1083,7 @@ public class RestClient extends BeanContext implements Closeable {
 										if (pt == PATH)
 											rc.path(pn, val, p.getSerializer(s), schema);
 										else if (val != null) {
-											if (pt == QUERY) 
+											if (pt == QUERY)
 												rc.query(pn, val, sie, ps, schema);
 											else if (pt == FORMDATA)
 												rc.formData(pn, val, sie, ps, schema);
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 161b7ad..701f8f9 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
@@ -45,6 +45,7 @@ import org.apache.juneau.jsonschema.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.plaintext.*;
+import org.apache.juneau.remoteable.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.converters.*;
 import org.apache.juneau.rest.exception.*;
@@ -3234,8 +3235,8 @@ public final class RestContext extends BeanContext {
 						if ("PROXY".equals(httpMethod)) {
 
 							final ClassMeta<?> interfaceClass = beanContext.getClassMeta(method.getGenericReturnType());
-							final Map<String,Method> remoteableMethods = interfaceClass.getRemoteableMethods();
-							if (remoteableMethods.isEmpty())
+							final RemoteableMeta rm = new RemoteableMeta(interfaceClass.getInnerClass(), "/foo");
+							if (rm.getMethodsByPath().isEmpty())
 								throw new RestException(SC_INTERNAL_SERVER_ERROR, "Method {0} returns an interface {1} that doesn't define any remoteable methods.", getMethodSignature(method), interfaceClass.getReadableName());
 
 							sm = new RestJavaMethod(resource, method, this) {
@@ -3250,15 +3251,16 @@ public final class RestContext extends BeanContext {
 									final Object o = res.getOutput();
 
 									if ("GET".equals(req.getMethod())) {
-										res.setOutput(getMethodInfo(remoteableMethods.values()));
+										res.setOutput(rm.getMethodsByPath().keySet());
 										return SC_OK;
 
 									} else if ("POST".equals(req.getMethod())) {
 										if (pathInfo.indexOf('/') != -1)
 											pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1);
 										pathInfo = urlDecode(pathInfo);
-										java.lang.reflect.Method m = remoteableMethods.get(pathInfo);
-										if (m != null) {
+										RemoteableMethodMeta rmm = rm.getMethodMetaByPath(pathInfo);
+										if (rmm != null) {
+											Method m = rmm.getJavaMethod();
 											try {
 												// Parse the args and invoke the method.
 												Parser p = req.getBody().getParser();
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
index 297dcc6..94f9131 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
@@ -22,7 +22,6 @@ import static org.apache.juneau.rest.RestContext.*;
 import static org.apache.juneau.rest.util.RestUtils.*;
 import static org.apache.juneau.httppart.HttpPartType.*;
 
-import java.beans.*;
 import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
@@ -37,6 +36,7 @@ import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.httppart.bean.*;
 import org.apache.juneau.internal.*;
+import org.apache.juneau.internal.HttpUtils;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.exception.*;
@@ -195,34 +195,13 @@ public class RestJavaMethod implements Comparable<RestJavaMethod>  {
 				}
 
 				String p = m.path();
-				if (isEmpty(p)) {
-					p = method.getName();
-					if (m.name().equals("")) {
-						for (String t : new String[]{"get","put","post","delete","options","head","connect","trace","patch"}) {
-							if (p.startsWith(t) && (p.length() == t.length() || Character.isUpperCase(p.charAt(t.length())))) {
-								p = Introspector.decapitalize(p.substring(t.length()));
-								break;
-							}
-						}
-						if (! p.startsWith("/"))
-							p = "/" + p;
-					}
-				}
+				if (isEmpty(p))
+					p = HttpUtils.detectHttpPath(method, true);
 
 				httpMethod = m.name().toUpperCase(Locale.ENGLISH);
-				if (httpMethod.equals("") && method.getName().startsWith("do"))
-					httpMethod = method.getName().substring(2).toUpperCase(Locale.ENGLISH);
-				if (httpMethod.equals("")) {
-					String mn = method.getName();
-					httpMethod = "GET";
-					for (String t : new String[]{"get","put","post","delete","options","head","connect","trace","patch"}) {
-						if (mn.startsWith(t) && (mn.length() == t.length() || Character.isUpperCase(mn.charAt(t.length())))) {
-							httpMethod = t.toUpperCase();
-							break;
-						}
-					}
-				}
-				if (httpMethod.equals("METHOD"))
+				if (httpMethod.isEmpty())
+					httpMethod = HttpUtils.detectHttpMethod(method, true, "GET");
+				if ("METHOD".equals(httpMethod))
 					httpMethod = "*";
 
 				priority = m.priority();
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
index b44364a..7a2493d 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
@@ -22,7 +22,6 @@ import java.util.*;
 import java.util.Map;
 import java.util.concurrent.*;
 
-import org.apache.juneau.*;
 import org.apache.juneau.dto.*;
 import org.apache.juneau.dto.html5.*;
 import org.apache.juneau.http.*;
@@ -30,6 +29,7 @@ import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.http.annotation.Header;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
+import org.apache.juneau.remoteable.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.exception.*;
@@ -52,7 +52,7 @@ import org.apache.juneau.rest.exception.*;
 @SuppressWarnings({"serial","javadoc"})
 public abstract class RemoteableServlet extends BasicRestServlet {
 
-	private final Map<String,Class<?>> classNameMap = new ConcurrentHashMap<>();
+	private final Map<String,RemoteableMeta> serviceMap = new ConcurrentHashMap<>();
 
 	//--------------------------------------------------------------------------------
 	// Abstract methods
@@ -81,10 +81,8 @@ public abstract class RemoteableServlet extends BasicRestServlet {
 	)
 	public List<LinkString> getInterfaces() throws Exception {
 		List<LinkString> l = new LinkedList<>();
-		boolean useAll = ! useOnlyAnnotated();
 		for (Class<?> c : getServiceMap().keySet())
-			if (useAll || getContext().getBeanContext().getClassMeta(c).isRemoteable())
-				l.add(new LinkString(c.getName(), "servlet:/{0}", urlEncode(c.getName())));
+			l.add(new LinkString(c.getName(), "servlet:/{0}", urlEncode(c.getName())));
 		return l;
 	}
 
@@ -125,13 +123,13 @@ public abstract class RemoteableServlet extends BasicRestServlet {
 		) throws NotFound, Exception {
 
 		// Find the method.
-		java.lang.reflect.Method m = getMethods(javaInterface).get(javaMethod);
-		if (m == null)
+		RemoteableMethodMeta rmm = getMethods(javaInterface).get(javaMethod);
+		if (rmm == null)
 			throw new NotFound("Method not found");
 
 		Table t = table();
 
-		Type[] types = m.getGenericParameterTypes();
+		Type[] types = rmm.getJavaMethod().getGenericParameterTypes();
 		if (types.length == 0) {
 			t.child(tr(td("No arguments").colspan(3).style("text-align:center")));
 		} else {
@@ -195,19 +193,20 @@ public abstract class RemoteableServlet extends BasicRestServlet {
 		// Find the parser.
 		if (p == null)
 			throw new UnsupportedMediaType("Could not find parser for media type ''{0}''", contentType);
-		Class<?> c = getInterfaceClass(javaInterface);
+		RemoteableMeta c = getInterfaceClass(javaInterface);
 
 		// Find the service.
-		Object service = getServiceMap().get(c);
+		Object service = getServiceMap().get(c.getJavaClass());
 		if (service == null)
 			throw new NotFound("Service not found");
 
 		// Find the method.
-		java.lang.reflect.Method m = getMethods(javaInterface).get(javaMethod);
-		if (m == null)
+		RemoteableMethodMeta rmm = getMethods(javaInterface).get(javaMethod);
+		if (rmm == null)
 			throw new NotFound("Method not found");
 
 		// Parse the args and invoke the method.
+		java.lang.reflect.Method m = rmm.getJavaMethod();
 		Object[] params = p.parseArgs(r, m.getGenericParameterTypes());
 		return m.invoke(service, params);
 	}
@@ -217,29 +216,25 @@ public abstract class RemoteableServlet extends BasicRestServlet {
 	// Other methods
 	//--------------------------------------------------------------------------------
 
-	private boolean useOnlyAnnotated() {
-		return getContext().getProperties().getBoolean(RemoteableServiceProperties.REMOTEABLE_includeOnlyRemotableMethods, false);
-	}
-
-	private Map<String,java.lang.reflect.Method> getMethods(String javaInterface) throws Exception {
-		Class<?> c = getInterfaceClass(javaInterface);
-		ClassMeta<?> cm = getContext().getBeanContext().getClassMeta(c);
-		return (useOnlyAnnotated() ? cm.getRemoteableMethods() : cm.getPublicMethods());
+	private Map<String,RemoteableMethodMeta> getMethods(String javaInterface) throws Exception {
+		return getInterfaceClass(javaInterface).getMethodsByPath();
 	}
 
 	/**
 	 * Return the <code>Class</code> given it's name if it exists in the services map.
 	 */
-	private Class<?> getInterfaceClass(String javaInterface) throws NotFound, Exception {
-		Class<?> c = classNameMap.get(javaInterface);
-		if (c == null) {
-			for (Class<?> c2 : getServiceMap().keySet())
-				if (c2.getName().equals(javaInterface)) {
-					classNameMap.put(javaInterface, c2);
-					return c2;
+	private RemoteableMeta getInterfaceClass(String javaInterface) throws NotFound, Exception {
+		RemoteableMeta rm = serviceMap.get(javaInterface);
+		if (rm == null) {
+			for (Class<?> c : getServiceMap().keySet()) {
+				if (c.getName().equals(javaInterface)) {
+					rm = new RemoteableMeta(c, null);
+					serviceMap.put(javaInterface, rm);
+					return rm;
 				}
+			}
 			throw new NotFound("Interface class not found");
 		}
-		return c;
+		return rm;
 	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/PoweredByJuneau.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/PoweredByJuneau.java
index f581635..e399d76 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/PoweredByJuneau.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/PoweredByJuneau.java
@@ -54,7 +54,7 @@ public class PoweredByJuneau extends Widget {
 	@Override /* Widget */
 	public String getHtml(RestRequest req) throws Exception {
 		UriResolver r = req.getUriResolver();
-		return "<a href='http://juneau.apache.org'><img style='float:right;padding-right:20px;height:32px' src='"+r.resolve("servlet:/htdocs/juneau.png")+"'>";
+		return "<a href='http://juneau.apache.org'><img style='float:right;padding-right:20px;height:32px' src='"+r.resolve("servlet:/htdocs/images/juneau.png")+"'>";
 	}
 }