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/21 09:01:25 UTC

incubator-juneau git commit: Support for @Query("*") and @Path on remoteable interfaces.

Repository: incubator-juneau
Updated Branches:
  refs/heads/master a3d7cc9af -> 3c1ff80f9


Support for @Query("*") and @Path on remoteable interfaces.

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

Branch: refs/heads/master
Commit: 3c1ff80f95a441a0358b403f9a4a9cb8e70f5209
Parents: a3d7cc9
Author: JamesBognar <ja...@apache.org>
Authored: Sun May 21 05:01:21 2017 -0400
Committer: JamesBognar <ja...@apache.org>
Committed: Sun May 21 05:01:21 2017 -0400

----------------------------------------------------------------------
 .../java/org/apache/juneau/BeanContext.java     |  12 +
 .../org/apache/juneau/remoteable/FormData.java  |  13 +-
 .../apache/juneau/remoteable/FormDataIfNE.java  |  11 +-
 .../org/apache/juneau/remoteable/Header.java    |  14 +-
 .../apache/juneau/remoteable/HeaderIfNE.java    |  11 +-
 .../java/org/apache/juneau/remoteable/Path.java |  63 ++++++
 .../org/apache/juneau/remoteable/Query.java     |  15 +-
 .../org/apache/juneau/remoteable/QueryIfNE.java |  12 +-
 .../juneau/remoteable/RemoteableMethodMeta.java |  17 +-
 juneau-core/src/main/javadoc/overview.html      |   6 +
 .../org/apache/juneau/rest/client/RestCall.java | 226 +++++++++++++------
 .../apache/juneau/rest/client/RestClient.java   |   5 +
 .../rest/test/ThirdPartyProxyResource.java      |  92 ++++++++
 .../juneau/rest/test/ThirdPartyProxyTest.java   |  93 ++++++++
 14 files changed, 505 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanContext.java b/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
index 33c1165..5b1f166 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanContext.java
@@ -1000,6 +1000,18 @@ public class BeanContext extends Context {
 	}
 
 	/**
+	 * Returns <jk>true</jk> if the specified object is a bean.
+	 *
+	 * @param o The object to test.
+	 * @return <jk>true</jk> if the specified object is a bean.  <jk>false</jk> if the bean is <jk>null</jk>.
+	 */
+	public boolean isBean(Object o) {
+		if (o == null)
+			return false;
+		return getClassMetaForObject(o).isBean();
+	}
+
+	/**
 	 * Prints meta cache statistics to <code>System.out</code>.
 	 */
 	protected static void dumpCacheStats() {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 5d0ecf2..780e64d 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
@@ -44,6 +44,8 @@ import org.apache.juneau.urlencoding.*;
  * 	<li><code>NameValuePairs</code> - Individual name-value pairs.
  * 	<li><code>Map&lt;String,Object&gt;</code> - Individual name-value pairs.
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
+ * 	<li>A bean - Individual name-value pairs.
+ * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * </ul>
  */
 @Documented
@@ -54,7 +56,14 @@ public @interface FormData {
 
 	/**
 	 * The form post parameter name.
-	 * Can be blank if the value is an instance of <code>NameValuePairs</code> or <code>Map&lt;String,Object&gt;</code>.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
 	 */
-	String value() default "";
+	String value() default "*";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 a156928..e38ac6a 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
@@ -28,7 +28,14 @@ public @interface FormDataIfNE {
 
 	/**
 	 * The form post parameter name.
-	 * Can be blank if the value is an instance of <code>NameValuePairs</code> or <code>Map&lt;String,Object&gt;</code>.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
 	 */
-	String value() default "";
+	String value() default "*";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 31eab82..d414eb1 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
@@ -37,9 +37,12 @@ import org.apache.juneau.urlencoding.*;
  * <p>
  * The argument can be any of the following types:
  * <ul class='spaced-list'>
+ * 	<li><code>NameValuePairs</code> - Individual name-value pairs.
  * 	<li>Any serializable POJO - Converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * 	<li><code>Map&lt;String,Object&gt;</code> - Individual name-value pairs.
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
+ * 	<li>A bean - Individual name-value pairs.
+ * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * </ul>
  */
 @Documented
@@ -50,7 +53,14 @@ public @interface Header {
 
 	/**
 	 * The HTTP header name.
-	 * Can be blank if the value is an instance of <code>Map&lt;String,Object&gt;</code>.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
 	 */
-	String value() default "";
+	String value() default "*";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 e99915e..350ddcf 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
@@ -28,7 +28,14 @@ public @interface HeaderIfNE {
 
 	/**
 	 * The HTTP header name.
-	 * Can be blank if the value is an instance of <code>Map&lt;String,Object&gt;</code>.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
 	 */
-	String value();
+	String value() default "*";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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
new file mode 100644
index 0000000..0420e54
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/remoteable/Path.java
@@ -0,0 +1,63 @@
+// ***************************************************************************************************************************
+// * 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.*;
+
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Annotation applied to Java method arguments of interface proxies to denote that they are path variables on the request.
+ * <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 myProxyMethod1(<ja>@Path</ja>(<js>"foo"</js>)</ja> String foo);
+ * 	}
+ * </p>
+ * <p>
+ * The argument can be any of the following types:
+ * <ul class='spaced-list'>
+ * 	<li><code>NameValuePairs</code> - Individual name-value pairs.
+ * 	<li>Any serializable POJO - Converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
+ * 	<li><code>Map&lt;String,Object&gt;</code> - Individual name-value pairs.
+ * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
+ * 	<li>A bean - Individual name-value pairs.
+ * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
+ * </ul>
+*/
+@Documented
+@Target(PARAMETER)
+@Retention(RUNTIME)
+@Inherited
+public @interface Path {
+
+	/**
+	 * The path parameter name.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
+	 */
+	String value() default "*";
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 0a82529..bd7e4e7 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
@@ -40,9 +40,12 @@ import org.apache.juneau.urlencoding.*;
  * <p>
  * The argument can be any of the following types:
  * <ul class='spaced-list'>
+ * 	<li><code>NameValuePairs</code> - Individual name-value pairs.
  * 	<li>Any serializable POJO - Converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * 	<li><code>Map&lt;String,Object&gt;</code> - Individual name-value pairs.
  * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
+ * 	<li>A bean - Individual name-value pairs.
+ * 		Values are converted to text using {@link UrlEncodingSerializer#serializePart(Object, Boolean, Boolean)}.
  * 	<li>{@link String} - Treated as a query string.
  * </ul>
 */
@@ -54,7 +57,15 @@ public @interface Query {
 
 	/**
 	 * The query parameter name.
-	 * Can be blank if the value is an instance of <code>Map&lt;String,Object&gt;</code> or <code>String</code>.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>String</code> - A complete query string.
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
 	 */
-	String value() default "";
+	String value() default "*";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 10ae263..59ac3d2 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
@@ -28,7 +28,15 @@ public @interface QueryIfNE {
 
 	/**
 	 * The query parameter name.
-	 * Can be blank if the value is an instance of <code>Map&lt;String,Object&gt;</code> or <code>String</code>.
+	 * <p>
+	 * A value of <js>"*"</js> indicates the value should be serialized as name/value pairs and is applicable
+	 * for the following data types:
+	 * <ul>
+	 * 	<li><code>String</code> - A complete query string.
+	 * 	<li><code>NameValuePairs</code>
+	 * 	<li><code>Map&lt;String,Object&gt;</code>
+	 * 	<li>A bean
+	 * </ul>
 	 */
-	String value();
+	String value() default "*";
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 12b0d6b..d48d42f 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
@@ -29,7 +29,7 @@ public class RemoteableMethodMeta {
 
 	private final String httpMethod;
 	private final String url;
-	private final RemoteMethodArg[] queryArgs, headerArgs, formDataArgs;
+	private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, formDataArgs;
 	private final Integer[] otherArgs;
 	private final Integer bodyArg;
 
@@ -43,6 +43,7 @@ public class RemoteableMethodMeta {
 		Builder b = new Builder(restUrl, m);
 		this.httpMethod = b.httpMethod;
 		this.url = b.url;
+		this.pathArgs = b.pathArgs.toArray(new RemoteMethodArg[b.pathArgs.size()]);
 		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()]);
@@ -53,6 +54,7 @@ public class RemoteableMethodMeta {
 	private static class Builder {
 		private String httpMethod, url;
 		private List<RemoteMethodArg>
+			pathArgs = new LinkedList<RemoteMethodArg>(),
 			queryArgs = new LinkedList<RemoteMethodArg>(),
 			headerArgs = new LinkedList<RemoteMethodArg>(),
 			formDataArgs = new LinkedList<RemoteMethodArg>();
@@ -83,7 +85,10 @@ public class RemoteableMethodMeta {
 				boolean annotated = false;
 				for (Annotation a : aa) {
 					Class<?> ca = a.annotationType();
-					if (ca == Query.class) {
+					if (ca == Path.class) {
+						Path p = (Path)a;
+						annotated = pathArgs.add(new RemoteMethodArg(p.value(), index, false));
+					} else if (ca == Query.class) {
 						Query q = (Query)a;
 						annotated = queryArgs.add(new RemoteMethodArg(q.value(), index, false));
 					} else if (ca == QueryIfNE.class) {
@@ -136,6 +141,14 @@ public class RemoteableMethodMeta {
 	}
 
 	/**
+	 * Returns the {@link Path @Path} annotated arguments on this Java method.
+	 * @return A map of {@link Path#value() @Path.value()} names to zero-indexed argument indices.
+	 */
+	public RemoteMethodArg[] getPathArgs() {
+		return pathArgs;
+	}
+
+	/**
 	 * Returns the {@link Query @Query} annotated arguments on this Java method.
 	 * @return A map of {@link Query#value() @Query.value()} names to zero-indexed argument indices.
 	 */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 7a90651..ff4ce10 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -6253,6 +6253,12 @@
 
 		<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>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},
+				{@link org.apache.juneau.remoteable.Query @Query},{@link org.apache.juneau.remoteable.QueryIfNE @QueryIfNE},
+				{@link org.apache.juneau.remoteable.Header @Header},{@link org.apache.juneau.remoteable.HeaderIfNE @HeaderIfNE}, 
 		</ul>
 
 		<h6 class='topic'>org.apache.juneau.microservice</h6>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 5cdcd84..147b9e4 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
@@ -172,29 +172,33 @@ public final class RestCall {
 	 * Adds a query parameter to the URI query.
 	 *
 	 * @param name The parameter name.
-	 * Can be null/blank if the value is a {@link Map} or {@link String}.
+	 * 	Can be null/blank/* if the value is a {@link Map}, {@link String}, {@link NameValuePairs}, or bean.
 	 * @param value The parameter value converted to a string using UON notation.
-	 * Can also be a {@link Map} or {@link String} if the name is null/blank.
-	 * If a {@link String} and the name is null/blank, then calls {@link URIBuilder#setCustomQuery(String)}.
+	 * 	Can also be {@link Map}, {@link String}, {@link NameValuePairs}, or bean if the name is null/blank/*.
+	 * 	If a {@link String} and the name is null/blank/*, then calls {@link URIBuilder#setCustomQuery(String)}.
 	 * @param skipIfEmpty Don't add the pair if the value is empty.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
 	@SuppressWarnings("unchecked")
-	public RestCall query(String name, Object value, boolean skipIfEmpty) {
-		if (! isEmpty(name)) {
+	public RestCall query(String name, Object value, boolean skipIfEmpty) throws RestCallException {
+		if (! ("*".equals(name) || isEmpty(name))) {
 			if (! (isEmpty(value) && skipIfEmpty))
 				uriBuilder.addParameter(name, client.getUrlEncodingSerializer().serializePart(value, false, null));
+		} else if (value instanceof NameValuePairs) {
+			for (NameValuePair p : (NameValuePairs)value)
+				query(p.getName(), p.getValue(), skipIfEmpty);
+		} else if (value instanceof String) {
+			String s = value.toString();
+			if (! isEmpty(s))
+				uriBuilder.setCustomQuery(s);
+		} else if (value instanceof Map) {
+			for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
+				query(p.getKey(), p.getValue(), skipIfEmpty);
+		} else if (isBean(value)){
+			return query(name, toBeanMap(value));
 		} else {
-			if (value instanceof String) {
-				String s = value.toString();
-				if (! isEmpty(s))
-					uriBuilder.setCustomQuery(s);
-			} else if (value instanceof Map) {
-				for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
-					query(p.getKey(), p.getValue(), skipIfEmpty);
-			} else {
-				throw new RuntimeException("Invalid name passed to query(name,value,skipIfEmpty).");
-			}
+			throw new RuntimeException("Invalid name passed to query(name,value,skipIfEmpty).");
 		}
 		return this;
 	}
@@ -264,28 +268,29 @@ public final class RestCall {
 	 * Adds a form data pair to this request to perform a URL-encoded form post.
 	 *
 	 * @param name The parameter name.
-	 * Can be null/blank if the value is a {@link Map} or {@link NameValuePairs}.
+	 * 	Can be null/blank/* if the value is a {@link Map}, {@link NameValuePairs}, or bean.
 	 * @param value The parameter value converted to a string using UON notation.
-	 * Can also be a {@link Map} or {@link NameValuePairs}.
+	 * 	Can also be {@link Map}, {@link NameValuePairs}, or bean if the name is null/blank/*.
 	 * @param skipIfEmpty Don't add the pair if the value is empty.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
 	@SuppressWarnings("unchecked")
-	public RestCall formData(String name, Object value, boolean skipIfEmpty) {
+	public RestCall formData(String name, Object value, boolean skipIfEmpty) throws RestCallException {
 		if (formData == null)
 			formData = new NameValuePairs();
-		if (! isEmpty(name)) {
+		if (! ("*".equals(name) || isEmpty(name))) {
 			if (! (isEmpty(value) && skipIfEmpty))
 				formData.add(new SerializedNameValuePair(name, value, client.getUrlEncodingSerializer()));
+		} else if (value instanceof NameValuePairs) {
+			formData.addAll((NameValuePairs)value);
+		} else if (value instanceof Map) {
+			for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
+				formData(p.getKey(), p.getValue(), skipIfEmpty);
+		} else if (isBean(value)) {
+			return formData(name, toBeanMap(value));
 		} else {
-			if (value instanceof NameValuePairs) {
-				formData.addAll((NameValuePairs)value);
-			} else if (value instanceof Map) {
-				for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
-					formData(p.getKey(), p.getValue(), skipIfEmpty);
-			} else {
-				throw new RuntimeException("Invalid name passed to formData(name,value,skipIfEmpty).");
-			}
+			throw new RuntimeException("Invalid name passed to formData(name,value,skipIfEmpty).");
 		}
 		return this;
 	}
@@ -354,6 +359,37 @@ public final class RestCall {
 	}
 
 	/**
+	 * Replaces a variable of the form <js>"{name}"</js> in the URL path with the specified value.
+	 * @param name The path variable name.
+	 * @param value The replacement value.
+	 *
+	 * @return This object (for method chaining).
+	 * @throws RestCallException If variable could not be found in path.
+	 */
+	@SuppressWarnings("unchecked")
+	public RestCall path(String name, Object value) throws RestCallException {
+		String path = uriBuilder.getPath();
+		if (! ("*".equals(name) || isEmpty(name))) {
+			String var = "{" + name + "}";
+			if (path.indexOf(var) == -1)
+				throw new RestCallException("Path variable {"+name+"} was not found in path.");
+			String newPath = path.replace(var, client.getUrlEncodingSerializer().serializePart(value, false, null));
+			uriBuilder.setPath(newPath);
+		} else if (value instanceof NameValuePairs) {
+			for (NameValuePair p : (NameValuePairs)value)
+				path(p.getName(), p.getValue());
+		} else if (value instanceof Map) {
+			for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
+				path(p.getKey(), p.getValue());
+		} else if (isBean(value)) {
+			return path(name, toBeanMap(value));
+		} else {
+			throw new RuntimeException("Invalid name passed to path(name,value).");
+		}
+		return this;
+	}
+
+	/**
 	 * Sets the URI user info.
 	 *
 	 * @param userInfo The new URI user info.
@@ -436,19 +472,23 @@ public final class RestCall {
 	 * @param value The header value.
 	 * @param skipIfEmpty Don't add the header if the name is null/empty.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
 	@SuppressWarnings("unchecked")
-	public RestCall header(String name, Object value, boolean skipIfEmpty) {
-		if (! isEmpty(name)) {
+	public RestCall header(String name, Object value, boolean skipIfEmpty) throws RestCallException {
+		if (! ("*".equals(name) || isEmpty(name))) {
 			if (! (isEmpty(value) && skipIfEmpty))
 				request.setHeader(name, client.getUrlEncodingSerializer().serializePart(value, false, true));
+		} else if (value instanceof NameValuePairs) {
+			for (NameValuePair p : (NameValuePairs)value)
+				header(p.getName(), p.getValue(), skipIfEmpty);
+		} else if (value instanceof Map) {
+			for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
+				header(p.getKey(), p.getValue(), skipIfEmpty);
+		} else if (isBean(value)) {
+			return header(name, toBeanMap(value), skipIfEmpty);
 		} else {
-			if (value instanceof Map) {
-				for (Map.Entry<String,Object> p : ((Map<String,Object>) value).entrySet())
-					header(p.getKey(), p.getValue(), skipIfEmpty);
-			} else {
-				throw new RuntimeException("Invalid name passed to formData(name,value,skipIfEmpty).");
-			}
+			throw new RuntimeException("Invalid name passed to header(name,value,skipIfEmpty).");
 		}
 		return this;
 	}
@@ -461,8 +501,9 @@ public final class RestCall {
 	 * The name can be null/empty if the value is a {@link Map}.
 	 * @param value The header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall header(String name, Object value) {
+	public RestCall header(String name, Object value) throws RestCallException {
 		return header(name, value, false);
 	}
 
@@ -471,8 +512,9 @@ public final class RestCall {
 	 *
 	 * @param values The header values.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall headers(Map<String,Object> values) {
+	public RestCall headers(Map<String,Object> values) throws RestCallException {
 		return header(null, values, false);
 	}
 
@@ -485,8 +527,9 @@ public final class RestCall {
 	 * The name can be null/empty if the value is a {@link Map}.
 	 * @param value The header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall headerIfNE(String name, Object value) {
+	public RestCall headerIfNE(String name, Object value) throws RestCallException {
 		return header(name, value, true);
 	}
 
@@ -497,8 +540,9 @@ public final class RestCall {
 	 *
 	 * @param values The header values.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall headersIfNE(Map<String,Object> values) {
+	public RestCall headersIfNE(Map<String,Object> values) throws RestCallException {
 		return header(null, values, true);
 	}
 
@@ -509,8 +553,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall accept(Object value) {
+	public RestCall accept(Object value) throws RestCallException {
 		return header("Accept", value);
 	}
 
@@ -521,8 +566,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall acceptCharset(Object value) {
+	public RestCall acceptCharset(Object value) throws RestCallException {
 		return header("Accept-Charset", value);
 	}
 
@@ -533,8 +579,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall acceptEncoding(Object value) {
+	public RestCall acceptEncoding(Object value) throws RestCallException {
 		return header("Accept-Encoding", value);
 	}
 
@@ -545,8 +592,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall acceptLanguage(Object value) {
+	public RestCall acceptLanguage(Object value) throws RestCallException {
 		return header("Accept-Language", value);
 	}
 
@@ -557,8 +605,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall authorization(Object value) {
+	public RestCall authorization(Object value) throws RestCallException {
 		return header("Authorization", value);
 	}
 
@@ -569,8 +618,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall cacheControl(Object value) {
+	public RestCall cacheControl(Object value) throws RestCallException {
 		return header("Cache-Control", value);
 	}
 
@@ -581,8 +631,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall connection(Object value) {
+	public RestCall connection(Object value) throws RestCallException {
 		return header("Connection", value);
 	}
 
@@ -593,8 +644,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall contentLength(Object value) {
+	public RestCall contentLength(Object value) throws RestCallException {
 		return header("Content-Length", value);
 	}
 
@@ -605,8 +657,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall contentType(Object value) {
+	public RestCall contentType(Object value) throws RestCallException {
 		return header("Content-Type", value);
 	}
 
@@ -617,8 +670,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall date(Object value) {
+	public RestCall date(Object value) throws RestCallException {
 		return header("Date", value);
 	}
 
@@ -629,8 +683,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall expect(Object value) {
+	public RestCall expect(Object value) throws RestCallException {
 		return header("Expect", value);
 	}
 
@@ -641,8 +696,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall forwarded(Object value) {
+	public RestCall forwarded(Object value) throws RestCallException {
 		return header("Forwarded", value);
 	}
 
@@ -653,8 +709,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall from(Object value) {
+	public RestCall from(Object value) throws RestCallException {
 		return header("From", value);
 	}
 
@@ -665,8 +722,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall host(Object value) {
+	public RestCall host(Object value) throws RestCallException {
 		return header("Host", value);
 	}
 
@@ -677,8 +735,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall ifMatch(Object value) {
+	public RestCall ifMatch(Object value) throws RestCallException {
 		return header("If-Match", value);
 	}
 
@@ -689,8 +748,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall ifModifiedSince(Object value) {
+	public RestCall ifModifiedSince(Object value) throws RestCallException {
 		return header("If-Modified-Since", value);
 	}
 
@@ -701,8 +761,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall ifNoneMatch(Object value) {
+	public RestCall ifNoneMatch(Object value) throws RestCallException {
 		return header("If-None-Match", value);
 	}
 
@@ -713,8 +774,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall ifRange(Object value) {
+	public RestCall ifRange(Object value) throws RestCallException {
 		return header("If-Range", value);
 	}
 
@@ -725,8 +787,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall ifUnmodifiedSince(Object value) {
+	public RestCall ifUnmodifiedSince(Object value) throws RestCallException {
 		return header("If-Unmodified-Since", value);
 	}
 
@@ -737,8 +800,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall maxForwards(Object value) {
+	public RestCall maxForwards(Object value) throws RestCallException {
 		return header("Max-Forwards", value);
 	}
 
@@ -749,8 +813,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall origin(Object value) {
+	public RestCall origin(Object value) throws RestCallException {
 		return header("Origin", value);
 	}
 
@@ -761,8 +826,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall pragma(Object value) {
+	public RestCall pragma(Object value) throws RestCallException {
 		return header("Pragma", value);
 	}
 
@@ -773,8 +839,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall proxyAuthorization(Object value) {
+	public RestCall proxyAuthorization(Object value) throws RestCallException {
 		return header("Proxy-Authorization", value);
 	}
 
@@ -785,8 +852,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall range(Object value) {
+	public RestCall range(Object value) throws RestCallException {
 		return header("Range", value);
 	}
 
@@ -797,8 +865,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall referer(Object value) {
+	public RestCall referer(Object value) throws RestCallException {
 		return header("Referer", value);
 	}
 
@@ -809,8 +878,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall te(Object value) {
+	public RestCall te(Object value) throws RestCallException {
 		return header("TE", value);
 	}
 
@@ -821,8 +891,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall userAgent(Object value) {
+	public RestCall userAgent(Object value) throws RestCallException {
 		return header("User-Agent", value);
 	}
 
@@ -833,8 +904,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall upgrade(Object value) {
+	public RestCall upgrade(Object value) throws RestCallException {
 		return header("Upgrade", value);
 	}
 
@@ -845,8 +917,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall via(Object value) {
+	public RestCall via(Object value) throws RestCallException {
 		return header("Via", value);
 	}
 
@@ -857,8 +930,9 @@ public final class RestCall {
 	 *
 	 * @param value The new header value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall warning(Object value) {
+	public RestCall warning(Object value) throws RestCallException {
 		return header("Warning", value);
 	}
 
@@ -867,8 +941,9 @@ public final class RestCall {
 	 *
 	 * @param version The version string (e.g. <js>"1.2.3"</js>)
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall clientVersion(String version) {
+	public RestCall clientVersion(String version) throws RestCallException {
 		return header("X-Client-Version", version);
 	}
 
@@ -1827,9 +1902,18 @@ public final class RestCall {
 	 *
 	 * @param value The debug value.
 	 * @return This object (for method chaining).
+	 * @throws RestCallException
 	 */
-	public RestCall debug(boolean value) {
+	public RestCall debug(boolean value) throws RestCallException {
 		header("Debug", value);
 		return this;
 	}
+
+	private boolean isBean(Object o) throws RestCallException {
+		return getBeanContext().isBean(o);
+	}
+
+	private BeanMap<?> toBeanMap(Object o) throws RestCallException {
+		return getBeanContext().createSession().toBeanMap(o);
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 e5f9a32..b026ff4 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
@@ -537,6 +537,9 @@ public class RestClient extends CoreObject {
 							RestCall rc = (httpMethod.equals("POST") ? doPost(url) : doGet(url));
 							rc.serializer(serializer).parser(parser);
 
+							for (RemoteMethodArg a : rmm.getPathArgs())
+								rc.path(a.name, args[a.index]);
+
 							for (RemoteMethodArg a : rmm.getQueryArgs())
 								rc.query(a.name, args[a.index], a.skipIfNE);
 
@@ -598,6 +601,8 @@ public class RestClient extends CoreObject {
 				s = sb.toString();
 			}
 		}
+		if (s.indexOf('{') != -1)
+			s = s.replace("{", "%7B").replace("}", "%7D");
 		return new URI(s);
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/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 1d583ce..d039d30 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
@@ -447,6 +447,55 @@ public class ThirdPartyProxyResource extends ResourceJena {
 		return "OK";
 	}
 
+	@RestMethod(name="GET", path="/stringQuery1")
+	public String stringQuery1(
+			@Query("a") int a,
+			@Query("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="GET", path="/stringQuery2")
+	public String stringQuery2(
+			@Query("a") int a,
+			@Query("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="GET", path="/mapQuery")
+	public String mapQuery(
+			@Query("a") int a,
+			@Query("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="GET", path="/beanQuery")
+	public String beanQuery(
+			@Query("a") int a,
+			@Query("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+
 	//--------------------------------------------------------------------------------
 	// FormData tests
 	//--------------------------------------------------------------------------------
@@ -654,6 +703,49 @@ public class ThirdPartyProxyResource extends ResourceJena {
 		return "OK";
 	}
 
+
+	//--------------------------------------------------------------------------------
+	// Path tests
+	//--------------------------------------------------------------------------------
+
+	@RestMethod(name="POST", path="/pathVars1/{a}/{b}")
+	public String pathVars1(
+		@Path("a") int a,
+		@Path("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+
+	@RestMethod(name="POST", path="/pathVars2/{a}/{b}")
+	public String pathVars2(
+		@Path("a") int a,
+		@Path("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+	@RestMethod(name="POST", path="/pathVars3/{a}/{b}")
+	public String pathVars3(
+		@Path("a") int a,
+		@Path("b") String b
+		) throws Exception {
+
+		assertEquals(1, a);
+		assertEquals("foo", b);
+
+		return "OK";
+	}
+
+
 	//--------------------------------------------------------------------------------
 	// Test return types.
 	//--------------------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/3c1ff80f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/ThirdPartyProxyTest.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/ThirdPartyProxyTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/ThirdPartyProxyTest.java
index 2f69bb1..7b73358 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/ThirdPartyProxyTest.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/ThirdPartyProxyTest.java
@@ -300,6 +300,34 @@ public class ThirdPartyProxyTest extends RestTestcase {
 		assertEquals("OK", r);
 	}
 
+	@Test
+	public void b08_stringQuery1() throws Exception {
+		String r = proxy.stringQuery1("a=1&b=foo");
+		assertEquals("OK", r);
+	}
+
+	@Test
+	public void b09_stringQuery2() throws Exception {
+		String r = proxy.stringQuery2("a=1&b=foo");
+		assertEquals("OK", r);
+	}
+
+	@Test
+	public void b10_mapQuery() throws Exception {
+		String r = proxy.mapQuery(
+			new AMap<String,Object>().append("a", 1).append("b", "foo")
+		);
+		assertEquals("OK", r);
+	}
+
+	@Test
+	public void b11_beanQuery() throws Exception {
+		String r = proxy.beanQuery(
+			new ABean().init()
+		);
+		assertEquals("OK", r);
+	}
+
 	//--------------------------------------------------------------------------------
 	// FormData tests
 	//--------------------------------------------------------------------------------
@@ -1050,6 +1078,30 @@ public class ThirdPartyProxyTest extends RestTestcase {
 		proxy.setEnum1d3dListMap(new AMap<TestEnum,List<TestEnum[][][]>>().append(TestEnum.ONE, new AList<TestEnum[][][]>().append(new TestEnum[][][]{{{TestEnum.TWO,null},null},null}).append(null)));
 	}
 
+	// Path variables
+
+	@Test
+	public void f01_pathVars1() {
+		String r = proxy.pathVars1(1, "foo");
+		assertEquals("OK", r);
+	}
+
+	@Test
+	public void f02_pathVars2() {
+		String r = proxy.pathVars2(
+			new AMap<String,Object>().append("a", 1).append("b", "foo")
+		);
+		assertEquals("OK", r);
+	}
+
+	@Test
+	public void f03_pathVars3() {
+		String r = proxy.pathVars3(
+			new ABean().init()
+		);
+		assertEquals("OK", r);
+	}
+
 
 	//--------------------------------------------------------------------------------
 	// Proxy class
@@ -1224,6 +1276,27 @@ public class ThirdPartyProxyTest extends RestTestcase {
 			@Query("h8") Map<TestEnum,List<TestEnum[][][]>> h8
 		);
 
+		@RemoteMethod(httpMethod="GET", path="/stringQuery1")
+		String stringQuery1(
+			@Query() String q
+		);
+
+		@RemoteMethod(httpMethod="GET", path="/stringQuery2")
+		String stringQuery2(
+			@Query("*") String q
+		);
+
+		@RemoteMethod(httpMethod="GET", path="/mapQuery")
+		String mapQuery(
+			@Query("*") Map<String,Object> q
+		);
+
+		@RemoteMethod(httpMethod="GET", path="/beanQuery")
+		String beanQuery(
+			@Query("*") ABean q
+		);
+
+
 		//--------------------------------------------------------------------------------
 		// FormData tests
 		//--------------------------------------------------------------------------------
@@ -1308,6 +1381,26 @@ public class ThirdPartyProxyTest extends RestTestcase {
 		);
 
 		//--------------------------------------------------------------------------------
+		// Path tests
+		//--------------------------------------------------------------------------------
+
+		@RemoteMethod(httpMethod="POST", path="/pathVars1/{a}/{b}")
+		String pathVars1(
+			@Path("a") int a,
+			@Path("b") String b
+		);
+
+		@RemoteMethod(httpMethod="POST", path="/pathVars2/{a}/{b}")
+		String pathVars2(
+			@Path Map<String,Object> p
+		);
+
+		@RemoteMethod(httpMethod="POST", path="/pathVars3/{a}/{b}")
+		String pathVars3(
+			@Path ABean p
+		);
+
+		//--------------------------------------------------------------------------------
 		// Test return types.
 		//--------------------------------------------------------------------------------