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 2017/05/24 23:06:08 UTC

[2/2] incubator-juneau git commit: New @RequestBean remoteable annotation

New @RequestBean remoteable annotation

Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/788b8488
Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/788b8488
Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/788b8488

Branch: refs/heads/master
Commit: 788b848861d23b01de4c18d1667db62adcbb205b
Parents: cdebb74
Author: JamesBognar <ja...@apache.org>
Authored: Wed May 24 19:06:04 2017 -0400
Committer: JamesBognar <ja...@apache.org>
Committed: Wed May 24 19:06:04 2017 -0400

----------------------------------------------------------------------
 .../main/java/org/apache/juneau/BeanMeta.java   |    4 +-
 .../org/apache/juneau/BeanPropertyMeta.java     |   31 +-
 .../org/apache/juneau/internal/ClassUtils.java  |   44 +
 .../java/org/apache/juneau/remoteable/Body.java |   20 +-
 .../org/apache/juneau/remoteable/FormData.java  |   29 +-
 .../apache/juneau/remoteable/FormDataIfNE.java  |    2 +-
 .../org/apache/juneau/remoteable/Header.java    |   29 +-
 .../apache/juneau/remoteable/HeaderIfNE.java    |    2 +-
 .../java/org/apache/juneau/remoteable/Path.java |   28 +-
 .../org/apache/juneau/remoteable/Query.java     |   31 +-
 .../org/apache/juneau/remoteable/QueryIfNE.java |    2 +-
 .../juneau/remoteable/RemoteableMethodMeta.java |   18 +-
 .../apache/juneau/remoteable/RequestBean.java   |   85 +
 juneau-core/src/main/javadoc/overview.html      |   12 +
 .../org/apache/juneau/rest/client/RestCall.java |    2 +-
 .../apache/juneau/rest/client/RestClient.java   |   46 +
 .../rest/test/ThirdPartyProxyResource.java      |   92 +
 .../juneau/rest/test/ThirdPartyProxyTest.java   | 2279 +++++++++++++++---
 .../org/apache/juneau/rest/RestContext.java     |   16 +-
 19 files changed, 2446 insertions(+), 326 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java
index 6ca660c..cd1c0a6 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java
@@ -194,7 +194,7 @@ public class BeanMeta<T> {
 				if (ctx.isNotABean(c))
 					return "Class matches exclude-class list";
 
-				if (! cVis.isVisible(c.getModifiers()))
+				if (! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass()))
 					return "Class is not public";
 
 				if (c.isAnnotationPresent(BeanIgnore.class))
@@ -557,7 +557,7 @@ public class BeanMeta<T> {
 				Class<?>[] pt = m.getParameterTypes();
 				Class<?> rt = m.getReturnType();
 				boolean isGetter = false, isSetter = false;
-				BeanProperty bp = m.getAnnotation(BeanProperty.class);
+				BeanProperty bp = getMethodAnnotation(BeanProperty.class, m);
 				String bpName = bp == null ? "" : bp.name();
 				if (pt.length == 0) {
 					if (n.startsWith("get") && (! rt.equals(Void.TYPE))) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index 409373a..e520799 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -132,7 +132,7 @@ public class BeanPropertyMeta {
 			}
 
 			if (getter != null) {
-				BeanProperty p = getter.getAnnotation(BeanProperty.class);
+				BeanProperty p = getMethodAnnotation(BeanProperty.class, getter);
 				if (rawTypeMeta == null)
 					rawTypeMeta = f.resolveClassMeta(p, getter.getGenericReturnType(), typeVarImpls);
 				isUri |= (rawTypeMeta.isUri() || getter.isAnnotationPresent(org.apache.juneau.annotation.URI.class));
@@ -146,7 +146,7 @@ public class BeanPropertyMeta {
 			}
 
 			if (setter != null) {
-				BeanProperty p = setter.getAnnotation(BeanProperty.class);
+				BeanProperty p = getMethodAnnotation(BeanProperty.class, setter);
 				if (rawTypeMeta == null)
 					rawTypeMeta = f.resolveClassMeta(p, setter.getGenericParameterTypes()[0], typeVarImpls);
 				isUri |= (rawTypeMeta.isUri() || setter.isAnnotationPresent(org.apache.juneau.annotation.URI.class));
@@ -224,7 +224,7 @@ public class BeanPropertyMeta {
 			if (c == Null.class)
 				return null;
 			try {
-				if (ClassUtils.isParentClass(PojoSwap.class, c)) {
+				if (isParentClass(PojoSwap.class, c)) {
 					return (PojoSwap)c.newInstance();
 				}
 				throw new RuntimeException("TODO - Surrogate swaps not yet supported.");
@@ -892,17 +892,38 @@ public class BeanPropertyMeta {
 			appendAnnotations(a, field.getType(), l);
 		}
 		if (getter != null) {
-			addIfNotNull(l, getter.getAnnotation(a));
+			addIfNotNull(l, getMethodAnnotation(a, getter));
 			appendAnnotations(a, getter.getReturnType(), l);
 		}
 		if (setter != null) {
-			addIfNotNull(l, setter.getAnnotation(a));
+			addIfNotNull(l, getMethodAnnotation(a, setter));
 			appendAnnotations(a, setter.getReturnType(), l);
 		}
+
 		appendAnnotations(a, this.getBeanMeta().getClassMeta().getInnerClass(), l);
 		return l;
 	}
 
+	/**
+	 * Returns the specified annotation on the field or methods that define this property.
+	 * <p>
+	 * This method will search up the parent class/interface hierarchy chain to search for the annotation on
+	 * overridden getters and setters.
+	 *
+	 * @param a The annotation to search for.
+	 * @return The annotation, or <jk>null</jk> if it wasn't found.
+	 */
+	public <A extends Annotation> A getAnnotation(Class<A> a) {
+		A t = null;
+		if (field != null)
+			t = field.getAnnotation(a);
+		if (t == null && getter != null)
+			t = getMethodAnnotation(a, getter);
+		if (t == null && setter != null)
+			t = getMethodAnnotation(a, setter);
+		return t;
+	}
+
 	private Object transform(BeanSession session, Object o) throws SerializeException {
 		// First use swap defined via @BeanProperty.
 		if (swap != null)

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java
index 1f2788b..d176b92 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/ClassUtils.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.internal;
 
 import java.io.*;
+import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
 
@@ -329,6 +330,49 @@ public final class ClassUtils {
 		return Modifier.isPublic(c.getModifiers());
 	}
 
+	/**
+	 * Returns the specified annotation on the specified method.
+	 * <p>
+	 * Similar to {@link Method#getAnnotation(Class)}, but searches up the parent hierarchy for the annotation
+	 * defined on parent classes and interfaces.
+	 * <p>
+	 * Normally, annotations defined on methods of parent classes and interfaces are not inherited by the child
+	 * methods.
+	 * This utility method gets around that limitation by searching the class hierarchy for the "same" method.
+	 *
+	 * @param a The annotation to search for.
+	 * @param m The method to search.
+	 * @return The annotation, or <jk>null</jk> if it wasn't found.
+	 */
+	public static <T extends Annotation> T getMethodAnnotation(Class<T> a, Method m) {
+		T t = m.getAnnotation(a);
+		if (t != null)
+			return t;
+		Class<?> pc = m.getDeclaringClass().getSuperclass();
+		if (pc != null) {
+			for (Method m2 : pc.getDeclaredMethods()) {
+				if (isSameMethod(m, m2)) {
+					t = getMethodAnnotation(a, m2);
+					if (t != null)
+						return t;
+				}
+			}
+		}
+		for (Class<?> ic : m.getDeclaringClass().getInterfaces()) {
+			for (Method m2 : ic.getDeclaredMethods()) {
+				if (isSameMethod(m, m2)) {
+					t = getMethodAnnotation(a, m2);
+					if (t != null)
+						return t;
+				}
+			}
+		}
+		return null;
+	}
+
+	private static boolean isSameMethod(Method m1, Method m2) {
+		return m1.getName().equals(m2.getName()) && Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes());
+	}
 
 	/**
 	 * Locates the no-arg constructor for the specified class.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java
index a73b694..3266cfa 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Body.java
@@ -41,9 +41,27 @@ import org.apache.juneau.serializer.*;
  * 	<li><code>HttpEntity</code> - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
  * 	<li><code>NameValuePairs</code> - Converted to a URL-encoded FORM post.
  * </ul>
+ * <p>
+ * The annotation can also be applied to a bean property field or getter when the argument is annotated with
+ *  {@link RequestBean @RequestBean}:
+ * <p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@Remoteable</ja>(path=<js>"/myproxy"</js>)
+ * 	<jk>public interface</jk> MyProxy {
+ *
+ * 		<ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>)
+ * 		String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean);
+ * 	}
+ *
+ * 	<jk>public interface</jk> MyRequestBean {
+ * 		<ja>@Body</ja>
+ * 		MyPojo getMyPojo();
+ * 	}
+ * </p>
  */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface Body {}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java
index 780e64d..23983c3 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormData.java
@@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.urlencoding.*;
 
 /**
@@ -47,9 +48,35 @@ import org.apache.juneau.urlencoding.*;
  * 	<li>A bean - Individual name-value pairs.
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * </ul>
+ * <p>
+ * The annotation can also be applied to a bean property field or getter when the argument is annotated with
+ *  {@link RequestBean @RequestBean}:
+ * <p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@Remoteable</ja>(path=<js>"/myproxy"</js>)
+ * 	<jk>public interface</jk> MyProxy {
+ *
+ * 		<ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>)
+ * 		String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean);
+ * 	}
+ *
+ * 	<jk>public interface</jk> MyRequestBean {
+ * 		<ja>@FormData</ja>
+ * 		String getFoo();
+ *
+ * 		<ja>@FormData</ja>
+ * 		MyPojo getBar();
+ * 	}
+ * </p>
+ * <p>
+ * When used in a request bean, the {@link #value()} can be used to override the form data parameter name.
+ * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation.
+ * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the
+ * map or bean to be expanded to form data parameters.
  */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface FormData {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java
index e38ac6a..d043217 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/FormDataIfNE.java
@@ -21,7 +21,7 @@ import java.lang.annotation.*;
  * Identical to {@link FormData @FormData} except skips values if they're null/blank.
  */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface FormDataIfNE {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java
index d414eb1..3fae979 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Header.java
@@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.urlencoding.*;
 
 /**
@@ -44,9 +45,35 @@ import org.apache.juneau.urlencoding.*;
  * 	<li>A bean - Individual name-value pairs.
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * </ul>
+ * <p>
+ * The annotation can also be applied to a bean property field or getter when the argument is annotated with
+ *  {@link RequestBean @RequestBean}:
+ * <p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@Remoteable</ja>(path=<js>"/myproxy"</js>)
+ * 	<jk>public interface</jk> MyProxy {
+ *
+ * 		<ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>)
+ * 		String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean);
+ * 	}
+ *
+ * 	<jk>public interface</jk> MyRequestBean {
+ * 		<ja>@Header</ja>(<js>"Foo"</js>)
+ * 		String getFoo();
+ *
+ * 		<ja>@Header</ja>(<js>"Bar"</js>)
+ * 		MyPojo getBar();
+ * 	}
+ * </p>
+ * <p>
+ * When used in a request bean, the {@link #value()} can be used to override the header name.
+ * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation.
+ * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the
+ * map or bean to be expanded to headers.
  */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface Header {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java
index 350ddcf..2aaf711 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/HeaderIfNE.java
@@ -21,7 +21,7 @@ import java.lang.annotation.*;
  * Identical to {@link Header @Header} except skips values if they're null/blank.
  */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface HeaderIfNE {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java
index 0420e54..47c6eea 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java
@@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.urlencoding.*;
 
 /**
@@ -41,9 +42,32 @@ import org.apache.juneau.urlencoding.*;
  * 	<li>A bean - Individual name-value pairs.
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * </ul>
-*/
+ * <p>
+ * The annotation can also be applied to a bean property field or getter when the argument is annotated with
+ *  {@link RequestBean @RequestBean}:
+ * <p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@Remoteable</ja>(path=<js>"/myproxy"</js>)
+ * 	<jk>public interface</jk> MyProxy {
+ *
+ * 		<ja>@RemoteMethod</ja>(path=<js>"/mymethod1/{foo}"</js>)
+ * 		String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean);
+ * 	}
+ *
+ * 	<jk>public interface</jk> MyRequestBean {
+ * 		<ja>@Path</ja>
+ * 		String getFoo();
+ * 	}
+ * </p>
+ * <p>
+ * When used in a request bean, the {@link #value()} can be used to override the path variable name.
+ * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation.
+ * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the
+ * map or bean to be expanded to path variables.
+ */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface Path {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java
index bd7e4e7..5729412 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Query.java
@@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.urlencoding.*;
 
 /**
@@ -48,9 +49,35 @@ import org.apache.juneau.urlencoding.*;
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * 	<li>{@link String} - Treated as a query string.
  * </ul>
-*/
+ * <p>
+ * The annotation can also be applied to a bean property field or getter when the argument is annotated with
+ *  {@link RequestBean @RequestBean}:
+ * <p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@Remoteable</ja>(path=<js>"/myproxy"</js>)
+ * 	<jk>public interface</jk> MyProxy {
+ *
+ * 		<ja>@RemoteMethod</ja>(path=<js>"/mymethod"</js>)
+ * 		String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean);
+ * 	}
+ *
+ * 	<jk>public interface</jk> MyRequestBean {
+ * 		<ja>@Query</ja>
+ * 		String getFoo();
+ *
+ * 		<ja>@Query</ja>
+ * 		MyPojo getBar();
+ * 	}
+ * </p>
+ * <p>
+ * When used in a request bean, the {@link #value()} can be used to override the query parameter name.
+ * It can also be overridden via the {@link BeanProperty#name @BeanProperty.name()} annotation.
+ * A name of <js>"*"</js> where the bean property value is a map or bean will cause the individual entries in the
+ * map or bean to be expanded to query parameters.
+ */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface Query {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java
index 59ac3d2..c2cc2fd 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/QueryIfNE.java
@@ -21,7 +21,7 @@ import java.lang.annotation.*;
  * Identical to {@link Query @Query} except skips values if they're null/blank.
  */
 @Documented
-@Target(PARAMETER)
+@Target({PARAMETER,FIELD,METHOD})
 @Retention(RUNTIME)
 @Inherited
 public @interface QueryIfNE {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
index 9504d37..bad4686 100644
--- a/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
@@ -30,7 +30,7 @@ public class RemoteableMethodMeta {
 	private final String httpMethod;
 	private final String url;
 	private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs;
-	private final Integer[] otherArgs;
+	private final Integer[] requestBeanArgs, otherArgs;
 	private final Integer bodyArg;
 
 	/**
@@ -47,6 +47,7 @@ public class RemoteableMethodMeta {
 		this.queryArgs = b.queryArgs.toArray(new RemoteMethodArg[b.queryArgs.size()]);
 		this.formDataArgs = b.formDataArgs.toArray(new RemoteMethodArg[b.formDataArgs.size()]);
 		this.headerArgs = b.headerArgs.toArray(new RemoteMethodArg[b.headerArgs.size()]);
+		this.requestBeanArgs = b.requestBeanArgs.toArray(new Integer[b.requestBeanArgs.size()]);
 		this.otherArgs = b.otherArgs.toArray(new Integer[b.otherArgs.size()]);
 		this.bodyArg = b.bodyArg;
 	}
@@ -58,7 +59,9 @@ public class RemoteableMethodMeta {
 			queryArgs = new LinkedList<RemoteMethodArg>(),
 			headerArgs = new LinkedList<RemoteMethodArg>(),
 			formDataArgs = new LinkedList<RemoteMethodArg>();
-		private List<Integer> otherArgs = new LinkedList<Integer>();
+		private List<Integer>
+			requestBeanArgs = new LinkedList<Integer>(),
+			otherArgs = new LinkedList<Integer>();
 		private Integer bodyArg;
 
 		private Builder(String restUrl, Method m) {
@@ -106,6 +109,9 @@ public class RemoteableMethodMeta {
 					} else if (ca == HeaderIfNE.class) {
 						HeaderIfNE h = (HeaderIfNE)a;
 						annotated = headerArgs.add(new RemoteMethodArg(h.value(), index, true));
+					} else if (ca == RequestBean.class) {
+						annotated = true;
+						requestBeanArgs.add(index);
 					} else if (ca == Body.class) {
 						annotated = true;
 						if (bodyArg == null)
@@ -173,6 +179,14 @@ public class RemoteableMethodMeta {
 	}
 
 	/**
+	 * Returns the {@link RequestBean @RequestBean} annotated arguments on this Java method.
+	 * @return A list of zero-indexed argument indices.
+	 */
+	public Integer[] getRequestBeanArgs() {
+		return requestBeanArgs;
+	}
+
+	/**
 	 * Returns the remaining non-annotated arguments on this Java method.
 	 * @return A list of zero-indexed argument indices.
 	 */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java b/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java
new file mode 100644
index 0000000..3da7f6c
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/RequestBean.java
@@ -0,0 +1,85 @@
+// ***************************************************************************************************************************
+// * 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.remoteable;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation applied to Java method arguments of interface proxies to denote a bean with remoteable annotations.
+ * <p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@Remoteable</ja>(path=<js>"/myproxy"</js>)
+ * 	<jk>public interface</jk> MyProxy {
+ *
+ * 		<ja>@RemoteMethod</ja>(path=<js>"/mymethod/{p1}/{p2}"</js>)
+ * 		String myProxyMethod(<ja>@RequestBean</ja> MyRequestBean bean);
+ * 	}
+ *
+ * 	<jk>public interface</jk> MyRequestBean {
+ *
+ * 		<ja>@Path</ja>
+ * 		String getP1();
+ *
+ * 		<ja>@Path</ja>(<js>"p2"</js>)
+ * 		String getX();
+ *
+ * 		<ja>@Query</ja>
+ * 		String getQ1();
+ *
+ * 		<ja>@Query</ja>
+ * 		<ja>@BeanProperty</ja>(name=<js>"q2"</js>)
+ * 		String getQuery2();
+ *
+ * 		<ja>@QueryIfNE</ja>(<js>"q3"</js>)
+ * 		String getQuery3();
+ *
+ * 		<ja>@QueryIfNE</ja>
+ * 		Map&lt;String,Object&gt; getExtraQueries();
+ *
+ * 		<ja>@FormData</ja>
+ * 		String getF1();
+ *
+ * 		<ja>@FormData</ja>
+ * 		<ja>@BeanProperty</ja>(name=<js>"f2"</js>)
+ * 		String getFormData2();
+ *
+ * 		<ja>@FormDataIfNE</ja>(<js>"f3"</js>)
+ * 		String getFormData3();
+ *
+ * 		<ja>@FormDataIfNE</ja>
+ * 		Map&lt;String,Object&gt; getExtraFormData();
+ *
+ * 		<ja>@Header</ja>
+ * 		String getH1();
+ *
+ * 		<ja>@Header</ja>
+ * 		<ja>@BeanProperty</ja>(name=<js>"H2"</js>)
+ * 		String getHeader2();
+ *
+ * 		<ja>@HeaderIfNE</ja>(<js>"H3"</js>)
+ * 		String getHeader3();
+ *
+ * 		<ja>@HeaderIfNE</ja>
+ * 		Map&lt;String,Object&gt; getExtraHeaders();
+ * 	}
+ * </p>
+ */
+@Documented
+@Target(PARAMETER)
+@Retention(RUNTIME)
+@Inherited
+public @interface RequestBean {}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-core/src/main/javadoc/overview.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/javadoc/overview.html b/juneau-core/src/main/javadoc/overview.html
index ff4ce10..f5b4f86 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -6126,6 +6126,16 @@
 			<li>New {@link org.apache.juneau.html.annotation.Html#render() @Html.render()} annotation and {@link org.apache.juneau.html.HtmlRender} class that allows you
 				to customize the HTML output and CSS style on bean properties:<br>
 				<img class='bordered' src='doc-files/HtmlRender_1.png'>
+			<li>{@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotation can now be applied to getters
+				and setters defined on interfaces.
+			<li>New methods on {@link org.apache.juneau.serializer.SerializerSession} and {@link org.apache.juneau.parser.ParserSession}
+				for retrieving context and runtime-override properties:
+				<ul>
+					<li>{@link org.apache.juneau.Session#getProperty(String)}
+					<li>{@link org.apache.juneau.Session#getProperty(String,String)}
+					<li>{@link org.apache.juneau.Session#getProperty(Class,String)}
+					<li>{@link org.apache.juneau.Session#getProperty(Class,String,Object)}
+				</ul>	
 		</ul>
 
 		<h6 class='topic'>org.apache.juneau.rest</h6>
@@ -6254,6 +6264,8 @@
 		<h6 class='topic'>org.apache.juneau.rest.client</h6>
 		<ul class='spaced-list'>
 			<li>New {@link org.apache.juneau.remoteable.Path @Path} annotation for specifying path variables on remoteable interfaces.
+			<li>New {@link org.apache.juneau.remoteable.RequestBean @RequestBean} annotation for specifying beans with remoteable annotations
+				defined on properties.
 			<li>The following annotations (and related methods on RestCall) can now take <code>NameValuePairs</code> and beans as input 
 				when using <js>"*"</js> as the name.
 				<br>{@link org.apache.juneau.remoteable.FormData @FormData},{@link org.apache.juneau.remoteable.FormDataIfNE @FormDataIfNE},

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index 14c7207..f06a172 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -198,7 +198,7 @@ public final class RestCall {
 		} else if (isBean(value)){
 			return query(name, toBeanMap(value), skipIfEmpty);
 		} else {
-			throw new RuntimeException("Invalid name passed to query(name,value,skipIfEmpty).");
+			throw new RuntimeException("Invalid name passed to query(name,value,skipIfEmpty): ("+name+","+value+","+skipIfEmpty+")");
 		}
 		return this;
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index b026ff4..dbcc294 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -552,6 +552,46 @@ public class RestClient extends CoreObject {
 							if (rmm.getBodyArg() != null)
 								rc.input(args[rmm.getBodyArg()]);
 
+							if (rmm.getRequestBeanArgs().length > 0) {
+								BeanSession bs = getBeanContext().createSession();
+
+								for (Integer i : rmm.getRequestBeanArgs()) {
+									BeanMap<?> bm = bs.toBeanMap(args[i]);
+									for (BeanPropertyValue bpv : bm.getValues(true)) {
+										BeanPropertyMeta pMeta = bpv.getMeta();
+										Object val = bpv.getValue();
+
+										Path p = pMeta.getAnnotation(Path.class);
+										if (p != null)
+											rc.path(getName(p.value(), pMeta), val);
+
+										Query q1 = pMeta.getAnnotation(Query.class);
+										if (q1 != null)
+											rc.query(getName(q1.value(), pMeta), val, false);
+
+										QueryIfNE q2 = pMeta.getAnnotation(QueryIfNE.class);
+										if (q2 != null)
+											rc.query(getName(q2.value(), pMeta), val, true);
+
+										FormData f1 = pMeta.getAnnotation(FormData.class);
+										if (f1 != null)
+											rc.formData(getName(f1.value(), pMeta), val, false);
+
+										FormDataIfNE f2 = pMeta.getAnnotation(FormDataIfNE.class);
+										if (f2 != null)
+											rc.formData(getName(f2.value(), pMeta), val, true);
+
+										org.apache.juneau.remoteable.Header h1 = pMeta.getAnnotation(org.apache.juneau.remoteable.Header.class);
+										if (h1 != null)
+											rc.header(getName(h1.value(), pMeta), val, false);
+
+										HeaderIfNE h2 = pMeta.getAnnotation(HeaderIfNE.class);
+										if (h2 != null)
+											rc.header(getName(h2.value(), pMeta), val, true);
+									}
+								}
+							}
+
 							if (rmm.getOtherArgs().length > 0) {
 								Object[] otherArgs = new Object[rmm.getOtherArgs().length];
 								int i = 0;
@@ -576,6 +616,12 @@ public class RestClient extends CoreObject {
 		}
 	}
 
+	private static String getName(String name, BeanPropertyMeta pMeta) {
+		if ("*".equals(name) && ! pMeta.getClassMeta().isMapOrBean())
+			name = pMeta.getName();
+		return name;
+	}
+
 	private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*");
 
 	UrlEncodingSerializer getUrlEncodingSerializer() {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/788b8488/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java
index 4a48379..532f292 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/ThirdPartyProxyResource.java
@@ -1032,7 +1032,99 @@ public class ThirdPartyProxyResource extends ResourceJena {
 		return "OK";
 	}
 
+	//--------------------------------------------------------------------------------
+	// RequestBean tests
+	//--------------------------------------------------------------------------------
+
+	@RestMethod(name="POST", path="/reqBeanPath/{a}/{b}")
+	public String reqBeanPath(
+		@Path("a") int a,
+		@Path("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/reqBeanQuery")
+	public String reqBeanQuery(
+		@Query("a") int a,
+		@Query("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
 
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/reqBeanQueryIfNE")
+	public String reqBeanQueryIfNE(
+		@Query("a") String a,
+		@Query("b") String b,
+		@Query("c") String c
+		) throws Exception {
+
+		assertEquals("foo", a);
+		assertNull(b);
+		assertNull(c);
+
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/reqBeanFormData")
+	public String reqBeanFormData(
+		@FormData("a") int a,
+		@FormData("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/reqBeanFormDataIfNE")
+	public String reqBeanFormDataIfNE(
+		@FormData("a") String a,
+		@FormData("b") String b,
+		@FormData("c") String c
+		) throws Exception {
+
+		assertEquals("foo", a);
+		assertNull(b);
+		assertNull(c);
+
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/reqBeanHeader")
+	public String reqBeanHeader(
+		@Header("a") int a,
+		@Header("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/reqBeanHeaderIfNE")
+	public String reqBeanHeaderIfNE(
+		@Header("a") String a,
+		@Header("b") String b,
+		@Header("c") String c
+		) throws Exception {
+
+		assertEquals("foo", a);
+		assertNull(b);
+		assertNull(c);
+
+		return "OK";
+	}
 	//--------------------------------------------------------------------------------
 	// Test return types.
 	//--------------------------------------------------------------------------------