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/03/25 17:25:26 UTC

[1/2] incubator-juneau git commit: Add support for @RestMethod proxies.

Repository: incubator-juneau
Updated Branches:
  refs/heads/master 1174420cf -> 9db2e03fa


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index ab660b0..161751a 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -53,6 +53,7 @@ import org.apache.juneau.utils.*;
  * 	<li><a class="doclink" href="package-summary.html#RestClient">org.apache.juneau.rest.client &gt; REST client API</a> for more information and code examples.
  * </ul>
  */
+@SuppressWarnings("hiding")
 public final class RestCall {
 
 	private final RestClient client;                       // The client that created this call.
@@ -74,6 +75,9 @@ public final class RestCall {
 	private TeeOutputStream outputStreams = new TeeOutputStream();
 	private boolean isClosed = false;
 	private boolean isFailed = false;
+	private Object input;
+	private Serializer serializer;
+	private Parser parser;
 
 	/**
 	 * Constructs a REST call with the specified method name.
@@ -90,6 +94,8 @@ public final class RestCall {
 		this.retryOn = client.retryOn;
 		this.retries = client.retries;
 		this.retryInterval = client.retryInterval;
+		this.serializer = client.serializer;
+		this.parser = client.parser;
 	}
 
 	/**
@@ -107,17 +113,33 @@ public final class RestCall {
 	 * @throws RestCallException If a retry was attempted, but the entity was not repeatable.
 	 */
 	public RestCall input(final Object input) throws RestCallException {
+		this.input = input;
+		return this;
+	}
 
-		if (! (request instanceof HttpEntityEnclosingRequestBase))
-			throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null);
-
-		HttpEntity entity = (input instanceof HttpEntity) ? (HttpEntity)input : new RestRequestEntity(input, client.serializer);
-
-		((HttpEntityEnclosingRequestBase)request).setEntity(entity);
-
-		if (retries > 1 && ! entity.isRepeatable())
-			throw new RestCallException("Rest call set to retryable, but entity is not repeatable.");
+	/**
+	 * Specifies the serializer to use on this call.
+	 * <p>
+	 * Overrides the serializer specified on the {@link RestClient}.
+	 *
+	 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
+	 * @return This object (for method chaining).
+	 */
+	public RestCall serializer(Serializer serializer) {
+		this.serializer = serializer;
+		return this;
+	}
 
+	/**
+	 * Specifies the parser to use on this call.
+	 * <p>
+	 * Overrides the parser specified on the {@link RestClient}.
+	 *
+	 * @param parser The parser used to parse POJOs from the body of the HTTP response.
+	 * @return This object (for method chaining).
+	 */
+	public RestCall parser(Parser parser) {
+		this.parser = parser;
 		return this;
 	}
 
@@ -520,13 +542,14 @@ public final class RestCall {
 	 * @return This object (for method chaining).
 	 * @throws RestCallException If current entity is not repeatable.
 	 */
-	@SuppressWarnings("hiding")
 	public RestCall retryable(int retries, long interval, RetryOn retryOn) throws RestCallException {
 		if (request instanceof HttpEntityEnclosingRequestBase) {
-		HttpEntity e = ((HttpEntityEnclosingRequestBase)request).getEntity();
-		if (e != null && ! e.isRepeatable())
-			throw new RestCallException("Attempt to make call retryable, but entity is not repeatable.");
-		}
+			if (input != null && input instanceof HttpEntity) {
+				HttpEntity e = (HttpEntity)input;
+				if (e != null && ! e.isRepeatable())
+					throw new RestCallException("Attempt to make call retryable, but entity is not repeatable.");
+				}
+			}
 		this.retries = retries;
 		this.retryInterval = interval;
 		this.retryOn = (retryOn == null ? RetryOn.DEFAULT : retryOn);
@@ -885,6 +908,16 @@ public final class RestCall {
 		isConnected = true;
 
 		try {
+
+			if (input != null) {
+				if (! (request instanceof HttpEntityEnclosingRequestBase))
+					throw new RestCallException(0, "Method does not support content entity.", request.getMethod(), request.getURI(), null);
+				HttpEntity entity = (input instanceof HttpEntity) ? (HttpEntity)input : new RestRequestEntity(input, getSerializer());
+				((HttpEntityEnclosingRequestBase)request).setEntity(entity);
+				if (retries > 1 && ! entity.isRepeatable())
+					throw new RestCallException("Rest call set to retryable, but entity is not repeatable.");
+			}
+
 			int sc = 0;
 			while (retries > 0) {
 				retries--;
@@ -1028,9 +1061,9 @@ public final class RestCall {
 	 * @throws RestCallException If no parser was defined on the client.
 	 */
 	protected Parser getParser() throws RestCallException {
-		if (client.parser == null)
+		if (parser == null)
 			throw new RestCallException(0, "No parser defined on client", request.getMethod(), request.getURI(), null);
-		return client.parser;
+		return parser;
 	}
 
 	/**
@@ -1040,9 +1073,9 @@ public final class RestCall {
 	 * @throws RestCallException If no serializer was defined on the client.
 	 */
 	protected Serializer getSerializer() throws RestCallException {
-		if (client.serializer == null)
+		if (serializer == null)
 			throw new RestCallException(0, "No serializer defined on client", request.getMethod(), request.getURI(), null);
-		return client.serializer;
+		return serializer;
 	}
 
 	/**
@@ -1114,13 +1147,33 @@ public final class RestCall {
 	}
 
 	/**
-	 * Converts the output from the connection into an object of the specified class using the registered {@link Parser}.
+	 * Same as {@link #getResponse(Type, Type...)} except optimized for a non-parameterized class.
+	 * <p>
+	 * This is the preferred parse method for simple types since you don't need to cast the results.
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a string.</jc>
+	 * 	String s = restClient.doGet(url).getResponse(String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a bean.</jc>
+	 * 	MyBean b = restClient.doGet(url).getResponse(MyBean.<jk>class</jk>);
 	 *
-	 * @param type The class to convert the input to.
-	 * @param <T> The class to convert the input to.
-	 * @return The parsed output.
+	 * 	<jc>// Parse into a bean array.</jc>
+	 * 	MyBean[] ba = restClient.doGet(url).getResponse(MyBean[].<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a linked-list of objects.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map of object keys/values.</jc>
+	 * 	Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>);
+	 * </p>
+	 *
+	 * @param <T> The class type of the object being created.
+	 * See {@link #getResponse(Type, Type...)} for details.
+	 * @param type The object type to create.
+	 * @return The parsed object.
+	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
 	 * @throws IOException If a connection error occurred.
-	 * @throws ParseException If the input contains a syntax error or is malformed for the <code>Content-Type</code> header.
 	 */
 	public <T> T getResponse(Class<T> type) throws IOException, ParseException {
 		BeanContext bc = getParser().getBeanContext();
@@ -1130,28 +1183,48 @@ public final class RestCall {
 	}
 
 	/**
-	 * Same as {@link #getResponse(Class)}, but useful for parsing into maps and collections of specific types.
+	 * Parses HTTP body into the specified object type.
+	 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps).
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Parse into a linked-list of strings.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a linked-list of beans.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>);
 	 *
-	 * @param type The class to resolve.
-	 * Can be any of the following:
+	 * 	<jc>// Parse into a linked-list of linked-lists of strings.</jc>
+	 * 	List l = restClient.doGet(url).getResponse(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map of string keys/values.</jc>
+	 * 	Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>);
+	 *
+	 * 	<jc>// Parse into a map containing string keys and values of lists containing beans.</jc>
+	 * 	Map m = restClient.doGet(url).getResponse(TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>);
+	 * </p>
+	 * <p>
+	 * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type.
+	 * <p>
+	 * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types.
+	 * <p>
+	 * The array can be arbitrarily long to indicate arbitrarily complex data structures.
+	 * <p>
+	 * <h5 class='section'>Notes:</h5>
 	 * <ul>
-	 * 	<li>{@link ClassMeta}
-	 * 	<li>{@link Class}
-	 * 	<li>{@link ParameterizedType}
-	 * 	<li>{@link GenericArrayType}
+	 * 	<li>Use the {@link #getResponse(Class)} method instead if you don't need a parameterized map/collection.
 	 * </ul>
+	 *
+	 * @param <T> The class type of the object to create.
+	 * @param type The object type to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
 	 * @param args The type arguments of the class if it's a collection or map.
-	 * Can be any of the following:
-	 * <ul>
-	 * 	<li>{@link ClassMeta}
-	 * 	<li>{@link Class}
-	 * 	<li>{@link ParameterizedType}
-	 * 	<li>{@link GenericArrayType}
-	 * </ul>
-	 * @param <T> The class to convert the input to.
-	 * @return The parsed output.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 * @return The parsed object.
+	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
 	 * @throws IOException If a connection error occurred.
-	 * @throws ParseException If the input contains a syntax error or is malformed for the <code>Content-Type</code> header.
+	 * @see BeanSession#getClassMeta(Class) for argument syntax for maps and collections.
 	 */
 	@SuppressWarnings("unchecked")
 	public <T> T getResponse(Type type, Type...args) throws IOException, ParseException {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 9ff546a..6594ced 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -59,7 +59,6 @@ public class RestClient extends CoreObject {
 	private final UrlEncodingSerializer urlEncodingSerializer;  // Used for form posts only.
 	final Parser parser;
 	private final String remoteableServletUri;
-	private final Map<Method,String> remoteableServiceUriMap;
 	private final String rootUrl;
 	private volatile boolean isClosed = false;
 	private final StackTraceElement[] creationStack;
@@ -113,7 +112,6 @@ public class RestClient extends CoreObject {
 		this.headers = Collections.unmodifiableMap(h2);
 		this.interceptors = interceptors.toArray(new RestCallInterceptor[interceptors.size()]);
 		this.remoteableServletUri = remoteableServletUri;
-		this.remoteableServiceUriMap = new ConcurrentHashMap<Method,String>(remoteableServiceUriMap);
 		this.rootUrl = rootUri;
 		this.retryOn = retryOn;
 		this.retries = retries;
@@ -410,29 +408,65 @@ public class RestClient extends CoreObject {
 	 * @throws RuntimeException If the Remotable service URI has not been specified on this
 	 * 	client by calling {@link RestClientBuilder#remoteableServletUri(String)}.
 	 */
-	@SuppressWarnings("unchecked")
 	public <T> T getRemoteableProxy(final Class<T> interfaceClass) {
 		if (remoteableServletUri == null)
 			throw new RuntimeException("Remoteable service URI has not been specified.");
-		return (T)Proxy.newProxyInstance(
-			interfaceClass.getClassLoader(),
-			new Class[] { interfaceClass },
-			new InvocationHandler() {
-				@Override /* InvocationHandler */
-				public Object invoke(Object proxy, Method method, Object[] args) {
-					try {
-						String uri = remoteableServiceUriMap.get(method);
-						if (uri == null) {
-							// Constructing this string each time can be time consuming, so cache it.
-							uri = remoteableServletUri + '/' + interfaceClass.getName() + '/' + ClassUtils.getMethodSignature(method);
-							remoteableServiceUriMap.put(method, uri);
+		return getRemoteableProxy(interfaceClass, remoteableServletUri + '/' + interfaceClass.getName());
+	}
+
+	/**
+	 * Create a new proxy interface for the specified REST PROXY interface.
+	 *
+	 * @param interfaceClass The interface to create a proxy for.
+	 * @param proxyUrl The URL of the REST method annotated with <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>.
+	 * @return The new proxy interface.
+	 */
+	public <T> T getRemoteableProxy(final Class<T> interfaceClass, final Object proxyUrl) {
+		return getRemoteableProxy(interfaceClass, proxyUrl, serializer, parser);
+	}
+
+	/**
+	 * Same as {@link #getRemoteableProxy(Class, Object)} but allows you to override the serializer and parser used.
+	 *
+	 * @param interfaceClass The interface to create a proxy for.
+	 * @param proxyUrl The URL of the REST method annotated with <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>.
+	 * @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
+	 * @param parser The parser used to parse POJOs from the body of the HTTP response.
+	 * @return The new proxy interface.
+	 */
+	@SuppressWarnings({ "unchecked", "hiding" })
+	public <T> T getRemoteableProxy(final Class<T> interfaceClass, final Object proxyUrl, final Serializer serializer, final Parser parser) {
+		try {
+			return (T)Proxy.newProxyInstance(
+				interfaceClass.getClassLoader(),
+				new Class[] { interfaceClass },
+				new InvocationHandler() {
+
+					final Map<Method,String> uriCache = new ConcurrentHashMap<Method,String>();
+					final String uri = toURI(proxyUrl).toString();
+
+					@Override /* InvocationHandler */
+					public Object invoke(Object proxy, Method method, Object[] args) {
+
+						// Constructing this string each time can be time consuming, so cache it.
+						String u = uriCache.get(method);
+						if (u == null) {
+							try {
+								u = uri + '/' + URLEncoder.encode(ClassUtils.getMethodSignature(method), "utf-8");
+							} catch (UnsupportedEncodingException e) {}
+							uriCache.put(method, u);
+						}
+
+						try {
+							return doPost(u, args).serializer(serializer).parser(parser).getResponse(method.getGenericReturnType());
+						} catch (Exception e) {
+							throw new RuntimeException(e);
 						}
-						return doPost(uri, args).getResponse(method.getReturnType());
-					} catch (Exception e) {
-						throw new RuntimeException(e);
 					}
-				}
-		});
+			});
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 	private Pattern absUrlPattern = Pattern.compile("^\\w+\\:\\/\\/.*");

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
index 0075437..39a2f88 100644
--- a/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+++ b/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
@@ -1015,6 +1015,20 @@ public class RestClientBuilder extends CoreObjectBuilder {
 		return property(PARSER_fileCharset, value);
 	}
 
+	/**
+	 * When called, <code>No-Trace: true</code> is added to requests.
+	 * <p>
+	 * This gives the opportunity for the servlet to not log errors on invalid requests.
+	 * This is useful for testing purposes when you don't want your log file to show lots
+	 * of errors that are simply the results of testing.
+	 *
+	 * @return This object (for method chaining).
+	 */
+	public RestClientBuilder noTrace() {
+		return header("No-Trace", true);
+	}
+
+
 	@Override /* CoreObjectBuilder */
 	public RestClientBuilder beansRequireDefaultConstructor(boolean value) {
 		super.beansRequireDefaultConstructor(value);
@@ -1342,6 +1356,7 @@ public class RestClientBuilder extends CoreObjectBuilder {
 	@Override /* CoreObjectBuilder */
 	public RestClientBuilder debug(boolean value) {
 		super.debug(value);
+		header("Debug", value);
 		return this;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/pom.xml
----------------------------------------------------------------------
diff --git a/juneau-rest-test/pom.xml b/juneau-rest-test/pom.xml
index 0b613dd..7e5cbff 100644
--- a/juneau-rest-test/pom.xml
+++ b/juneau-rest-test/pom.xml
@@ -46,6 +46,7 @@
 		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
+			<scope>compile</scope>
 		</dependency>
 		<dependency>
 			<groupId>javax.ws.rs</groupId>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
new file mode 100644
index 0000000..6b96290
--- /dev/null
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxy.java
@@ -0,0 +1,62 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.test;
+
+import java.util.*;
+
+/**
+ * Interface proxy exposed in InterfaceProxyResource and tested in InterfaceProxyTest.
+ */
+public interface InterfaceProxy {
+
+	void returnVoid();
+	int returnInt();
+	Integer returnInteger();
+	float returnFloat();
+	Float returnFloatObject();
+	String returnString();
+	String returnNullString();
+	int[] returnIntArray();
+	String[] returnStringArray();
+	List<Integer> returnIntegerList();
+	List<String> returnStringList();
+	Bean returnBean();
+	Bean[] returnBeanArray();
+	List<Bean> returnBeanList();
+
+	void setNothing();
+	void setInt(int x);
+	void setInteger(Integer x);
+	void setFloat(float x);
+	void setFloatObject(Float x);
+	void setString(String x);
+	void setNullString(String x);
+	void setIntArray(int[] x);
+	void setStringArray(String[] x);
+	void setIntegerList(List<Integer> x);
+	void setStringList(List<String> x);
+	void setBean(Bean x);
+	void setBeanArray(Bean[] x);
+	void setBeanList(List<Bean> x);
+
+	public static class Bean {
+		public int a;
+		public String b;
+
+		Bean init() {
+			this.a = 1;
+			this.b = "foo";
+			return this;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
new file mode 100644
index 0000000..82de1c2
--- /dev/null
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/InterfaceProxyResource.java
@@ -0,0 +1,128 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.test;
+
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.jena.*;
+import org.junit.*;
+
+/**
+ * Tests inteface proxies exposed through <code>@RestMethod(name="PROXY")</code>
+ */
+@RestResource(
+	path="/testInterfaceProxyResource")
+public class InterfaceProxyResource extends RestServletJenaDefault {
+	private static final long serialVersionUID = 1L;
+
+	//====================================================================================================
+	// Test that Q-values are being resolved correctly.
+	//====================================================================================================
+	@RestMethod(name="PROXY", path="/proxy/*")
+	public InterfaceProxy getProxy() {
+		return new InterfaceProxy() {
+			@Override
+			public void returnVoid() {}
+			@Override
+			public Integer returnInteger() { return 1;}
+			@Override
+			public int returnInt() { return 1; }
+			@Override
+			public float returnFloat() { return 1f; }
+			@Override
+			public Float returnFloatObject() { return 1f; }
+			@Override
+			public String returnString() { return "foobar"; }
+			@Override
+			public String returnNullString() { return null; }
+			@Override
+			public int[] returnIntArray() { return new int[]{1,2}; }
+			@Override
+			public String[] returnStringArray() { return new String[]{"foo","bar",null};}
+			@Override
+			public List<Integer> returnIntegerList() { return Arrays.asList(new Integer[]{1,2}); }
+			@Override
+			public List<String> returnStringList() { return Arrays.asList(new String[]{"foo","bar",null}); }
+			@Override
+			public Bean returnBean() { return new Bean().init(); }
+			@Override
+			public Bean[] returnBeanArray() { return new Bean[]{new Bean().init()}; }
+			@Override
+			public List<Bean> returnBeanList() { return Arrays.asList(new Bean().init()); }
+
+			@Override
+			public void setNothing() {
+			}
+			@Override
+			public void setInt(int x) {
+				assertEquals(1, x);
+			}
+			@Override
+			public void setInteger(Integer x) {
+				assertEquals((Integer)1, x);
+			}
+			@Override
+			public void setFloat(float x) {
+				assertTrue(1f == x);
+			}
+			@Override
+			public void setFloatObject(Float x) {
+				assertTrue(1f == x);
+			}
+			@Override
+			public void setString(String x) {
+				assertEquals("foo", x);
+			}
+			@Override
+			public void setNullString(String x) {
+				assertNull(x);
+			}
+			@Override
+			public void setIntArray(int[] x) {
+				assertObjectEquals("[1,2]", x);
+			}
+			@Override
+			public void setStringArray(String[] x) {
+				assertObjectEquals("['foo','bar',null]", x);
+			}
+			@Override
+			public void setIntegerList(List<Integer> x) {
+				assertObjectEquals("[1,2,null]", x);
+			}
+			@Override
+			public void setStringList(List<String> x) {
+				assertObjectEquals("['foo','bar',null]", x);
+			}
+			@Override
+			public void setBean(Bean x) {
+				assertObjectEquals("{a:1,b:'foo'}", x);
+			}
+			@Override
+			public void setBeanArray(Bean[] x) {
+				assertObjectEquals("[{a:1,b:'foo'}]", x);
+			}
+			@Override
+			public void setBeanList(List<Bean> x) {
+				assertObjectEquals("[{a:1,b:'foo'}]", x);
+			}
+		};
+	}
+
+	private static void assertObjectEquals(String e, Object o) {
+		Assert.assertEquals(e, JsonSerializer.DEFAULT_LAX.toString(o));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
index f24c34c..ceeaf48 100644
--- a/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
+++ b/juneau-rest-test/src/main/java/org/apache/juneau/rest/test/Root.java
@@ -38,6 +38,7 @@ import org.apache.juneau.rest.labels.*;
 		InheritanceResource.TestParsers.class,
 		InheritanceResource.TestProperties.class,
 		InheritanceResource.TestSerializers.class,
+		InterfaceProxyResource.class,
 		LargePojosResource.class,
 		MessagesResource.Messages2Resource.class,
 		MessagesResource.class,

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
new file mode 100644
index 0000000..4ed272d
--- /dev/null
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/InterfaceProxyTest.java
@@ -0,0 +1,225 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.test;
+
+import static org.apache.juneau.rest.test.TestUtils.*;
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.apache.juneau.html.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.msgpack.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.test.InterfaceProxy.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.uon.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.xml.*;
+import org.junit.*;
+import org.junit.runner.*;
+import org.junit.runners.*;
+
+@RunWith(Parameterized.class)
+public class InterfaceProxyTest extends RestTestcase {
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> getParameters() {
+		return Arrays.asList(new Object[][] {
+			{ /* 0 */ "Json", JsonSerializer.DEFAULT, JsonParser.DEFAULT },
+			{ /* 1 */ "Xml", XmlSerializer.DEFAULT, XmlParser.DEFAULT },
+			{ /* 2 */ "Mixed", JsonSerializer.DEFAULT, XmlParser.DEFAULT },
+			{ /* 3 */ "Html", HtmlSerializer.DEFAULT, HtmlParser.DEFAULT },
+			{ /* 4 */ "MessagePack", MsgPackSerializer.DEFAULT, MsgPackParser.DEFAULT },
+			{ /* 5 */ "UrlEncoding", UrlEncodingSerializer.DEFAULT, UrlEncodingParser.DEFAULT },
+			{ /* 6 */ "Uon", UonSerializer.DEFAULT, UonParser.DEFAULT },
+		});
+	}
+
+	private Serializer serializer;
+	private Parser parser;
+
+	public InterfaceProxyTest(String label, Serializer serializer, Parser parser) {
+		this.serializer = serializer;
+		this.parser = parser;
+	}
+
+	private InterfaceProxy getProxy() {
+		return getClient(serializer, parser).getRemoteableProxy(InterfaceProxy.class, "/testInterfaceProxyResource/proxy");
+	}
+
+	@Test
+	public void returnVoid() {
+		getProxy().returnVoid();
+	}
+
+	@Test
+	public void returnInteger() {
+		assertEquals((Integer)1, getProxy().returnInteger());
+	}
+
+	@Test
+	public void returnInt() {
+		assertEquals(1, getProxy().returnInt());
+	}
+
+	@Test
+	public void returnFloat() {
+		assertTrue(1f == getProxy().returnFloat());
+	}
+
+	@Test
+	public void returnFloatObject() {
+		assertTrue(1f == getProxy().returnFloatObject());
+	}
+
+	@Test
+	public void returnString() {
+		assertEquals("foobar", getProxy().returnString());
+	}
+
+	@Test
+	public void returnNullString() {
+		assertNull(getProxy().returnNullString());
+	}
+
+	@Test
+	public void returnIntArray() {
+		assertObjectEquals("[1,2]", getProxy().returnIntArray());
+	}
+
+	@Test
+	public void returnStringArray() {
+		assertObjectEquals("['foo','bar',null]", getProxy().returnStringArray());
+	}
+
+	@Test
+	public void returnIntegerList() {
+		assertObjectEquals("[1,2]", getProxy().returnIntegerList());
+		assertTrue(getProxy().returnIntegerList().get(0) instanceof Integer);
+	}
+
+	@Test
+	public void returnStringList() {
+		assertObjectEquals("['foo','bar',null]", getProxy().returnStringList());
+		assertTrue(getProxy().returnStringList() instanceof List);
+	}
+
+	@Test
+	public void returnBean() {
+		assertObjectEquals("{a:1,b:'foo'}", getProxy().returnBean());
+		assertClass(InterfaceProxy.Bean.class, getProxy().returnBean());
+	}
+
+	@Test
+	public void returnBeanArray() {
+		assertObjectEquals("[{a:1,b:'foo'}]", getProxy().returnBeanArray());
+		assertClass(InterfaceProxy.Bean.class, getProxy().returnBeanArray()[0]);
+	}
+
+	@Test
+	public void returnBeanList() {
+		assertObjectEquals("[{a:1,b:'foo'}]", getProxy().returnBeanList());
+		assertClass(InterfaceProxy.Bean.class, getProxy().returnBeanList().get(0));
+	}
+
+	@Test
+	public void setNothing() {
+		getProxy().setNothing();
+	}
+
+	@Test
+	public void setInt() {
+		getProxy().setInt(1);
+	}
+
+	@Test
+	public void setWrongInt() {
+		try {
+			getProxy().setInt(2);
+			fail("Exception expected");
+		} catch (Exception e) {
+			// Good.
+		}
+	}
+
+	@Test
+	public void setInteger() {
+		getProxy().setInteger(1);
+	}
+
+	@Test
+	public void setFloat() {
+		getProxy().setFloat(1f);
+	}
+
+	@Test
+	public void setFloatObject() {
+		getProxy().setFloatObject(1f);
+	}
+
+	@Test
+	public void setString() {
+		getProxy().setString("foo");
+	}
+
+	@Test
+	public void setNullString() {
+		getProxy().setNullString(null);
+	}
+
+	@Test
+	public void setNullStringBad() {
+		try {
+			getProxy().setNullString("foo");
+			fail("Exception expected");
+		} catch (Exception e) {
+			// Good.
+		}
+	}
+
+	@Test
+	public void setIntArray() {
+		getProxy().setIntArray(new int[]{1,2});
+	}
+
+	@Test
+	public void setStringArray() {
+		getProxy().setStringArray(new String[]{"foo","bar",null});
+	}
+
+	@Test
+	public void setIntegerList() {
+		getProxy().setIntegerList(Arrays.asList(new Integer[]{1,2,null}));
+	}
+
+	@Test
+	public void setStringList() {
+		getProxy().setStringList(Arrays.asList("foo","bar",null));
+	}
+
+	@Test
+	public void setBean() {
+		getProxy().setBean(new Bean().init());
+	}
+
+	@Test
+	public void setBeanArray() {
+		getProxy().setBeanArray(new Bean[]{new Bean().init()});
+	}
+
+	@Test
+	public void setBeanList() {
+		getProxy().setBeanList(Arrays.asList(new Bean().init()));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
index 70d4a5d..1a23177 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/RestTestcase.java
@@ -12,6 +12,12 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.test;
 
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.client.*;
+import org.apache.juneau.serializer.*;
 import org.junit.*;
 
 /**
@@ -22,15 +28,33 @@ import org.junit.*;
 public class RestTestcase {
 
 	private static boolean microserviceStarted;
+	private static List<RestClient> clients = new ArrayList<RestClient>();
 
 	@BeforeClass
 	public static void setUp() {
 		microserviceStarted = TestMicroservice.startMicroservice();
 	}
 
+	/**
+	 * Creates a REST client against the test microservice using the specified serializer and parser.
+	 * Client is automatically closed on tear-down.
+	 */
+	protected RestClient getClient(Serializer serializer, Parser parser) {
+		RestClient rc = TestMicroservice.client(serializer, parser).build();
+		clients.add(rc);
+		return rc;
+	}
+
 	@AfterClass
 	public static void tearDown() {
 		if (microserviceStarted)
 			TestMicroservice.stopMicroservice();
+		for (RestClient rc : clients) {
+			try {
+				rc.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
index 25a740d..a2f0777 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestMicroservice.java
@@ -96,7 +96,7 @@ public class TestMicroservice {
 		try {
 			return new RestClientBuilder()
 				.rootUrl(microserviceURI)
-			//	.httpClient(createHttpClient(), true)
+				.noTrace()
 			;
 		} catch (Exception e) {
 			throw new RuntimeException(e);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
index caf9041..102c17e 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/TestUtils.java
@@ -37,6 +37,13 @@ public class TestUtils {
 	}
 
 	/**
+	 * Assert that the object is an instance of the specified class.
+	 */
+	public static void assertClass(Class<?> c, Object o) {
+		Assert.assertEquals(c, o == null ? null : o.getClass());
+	}
+
+	/**
 	 * Assert that the object equals the specified string after running it through ws.toString().
 	 */
 	public static void assertObjectEquals(String s, Object o, WriterSerializer ws) {

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
----------------------------------------------------------------------
diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
index 2eb975c..0714aca 100644
--- a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
+++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java
@@ -36,6 +36,7 @@ import org.junit.runners.Suite.*;
 	GroupsTest.class,
 	GzipTest.class,
 	InheritanceTest.class,
+	InterfaceProxyTest.class,
 	JacocoDummyTest.class,
 	LargePojosTest.class,
 	MessagesTest.class,

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
index 5cfa4e5..2fb2560 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/CallMethod.java
@@ -44,7 +44,7 @@ import org.apache.juneau.urlencoding.*;
  * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
  */
 @SuppressWarnings("hiding")
-final class CallMethod implements Comparable<CallMethod>  {
+class CallMethod implements Comparable<CallMethod>  {
 	private final java.lang.reflect.Method method;
 	private final String httpMethod;
 	private final UrlPathPattern pathPattern;
@@ -60,7 +60,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 	private final UrlEncodingSerializer urlEncodingSerializer;
 	private final ObjectMap properties;
 	private final Map<String,String> defaultRequestHeaders;
-	private final String defaultEncoding;
+	private final String defaultCharset;
 	private final boolean deprecated;
 	private final String description, tags, summary, externalDocs;
 	private final Integer priority;
@@ -86,7 +86,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 		this.urlEncodingSerializer = b.urlEncodingSerializer;
 		this.properties = b.properties;
 		this.defaultRequestHeaders = b.defaultRequestHeaders;
-		this.defaultEncoding = b.defaultEncoding;
+		this.defaultCharset = b.defaultCharset;
 		this.deprecated = b.deprecated;
 		this.description = b.description;
 		this.tags = b.tags;
@@ -98,7 +98,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 	}
 
 	private static class Builder  {
-		private String httpMethod, defaultEncoding, description, tags, summary, externalDocs;
+		private String httpMethod, defaultCharset, description, tags, summary, externalDocs;
 		private UrlPathPattern pathPattern;
 		private CallMethod.MethodParam[] params;
 		private RestGuard[] guards;
@@ -263,7 +263,7 @@ final class CallMethod implements Comparable<CallMethod>  {
 					defaultRequestHeaders.put(h[0], h[1]);
 				}
 
-				defaultEncoding = properties.getString(REST_defaultCharset, context.getDefaultCharset());
+				defaultCharset = properties.getString(REST_defaultCharset, context.getDefaultCharset());
 				String paramFormat = properties.getString(REST_paramFormat, context.getParamFormat());
 				plainParams = paramFormat.equals("PLAIN");
 
@@ -808,8 +808,8 @@ final class CallMethod implements Comparable<CallMethod>  {
 		for (int i = 0; i < pathPattern.getVars().length; i++)
 			req.setPathParameter(pathPattern.getVars()[i], patternVals[i]);
 
-		req.init(method, remainder, createRequestProperties(properties, req), defaultRequestHeaders, defaultEncoding, serializers, parsers, urlEncodingParser, encoders);
-		res.init(req.getProperties(), defaultEncoding, serializers, urlEncodingSerializer, encoders);
+		req.init(method, remainder, createRequestProperties(properties, req), defaultRequestHeaders, defaultCharset, serializers, parsers, urlEncodingParser, encoders);
+		res.init(req.getProperties(), defaultCharset, serializers, urlEncodingSerializer, encoders);
 
 		// Class-level guards
 		for (RestGuard guard : context.getGuards())

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
index 29113f4..89abdc4 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -399,14 +399,58 @@ public final class RestContext extends Context {
 							throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", this.getClass().getName(), method.getName());
 
 						CallMethod sm = new CallMethod(resource, method, this);
-						_javaRestMethods.put(method.getName(), sm);
-
 						String httpMethod = sm.getHttpMethod();
-						if (! routers.containsKey(httpMethod))
-							routers.put(httpMethod, new CallRouter.Builder(httpMethod));
-
-						routers.get(httpMethod).add(sm);
 
+						// PROXY is a special case where a method returns an interface that we
+						// can perform REST calls against.
+						// We override the CallMethod.invoke() method to insert our logic.
+						if ("PROXY".equals(httpMethod)) {
+
+							final ClassMeta<?> interfaceClass = beanContext.getClassMeta(method.getGenericReturnType());
+							sm = new CallMethod(resource, method, this) {
+
+								@Override
+								int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+
+									int rc = super.invoke(pathInfo, req, res);
+									if (rc != SC_OK)
+										return rc;
+
+									final Object o = res.getOutput();
+
+									if ("GET".equals(req.getMethod())) {
+										res.setOutput(ClassUtils.getMethodInfo(interfaceClass.getProxyableMethods().values()));
+										return SC_OK;
+
+									} else if ("POST".equals(req.getMethod())) {
+										if (pathInfo.indexOf('/') != -1)
+											pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1);
+										pathInfo = RestUtils.decode(pathInfo);
+										java.lang.reflect.Method m = interfaceClass.getProxyableMethods().get(pathInfo);
+										if (m != null) {
+											try {
+												// Parse the args and invoke the method.
+												Parser p = req.getParser();
+												Object input = p.isReaderParser() ? req.getReader() : req.getInputStream();
+												res.setOutput(m.invoke(o, p.parseArgs(input, m.getGenericParameterTypes())));
+												return SC_OK;
+											} catch (Exception e) {
+												throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+											}
+										}
+									}
+									return SC_NOT_FOUND;
+								}
+							};
+
+							_javaRestMethods.put(method.getName(), sm);
+							addToRouter(routers, "GET", sm);
+							addToRouter(routers, "POST", sm);
+
+						} else {
+							_javaRestMethods.put(method.getName(), sm);
+							addToRouter(routers, httpMethod, sm);
+						}
 					} catch (RestServletException e) {
 						throw new RestServletException("Problem occurred trying to serialize methods on class {0}, methods={1}", this.getClass().getName(), JsonSerializer.DEFAULT_LAX.serialize(methodsFound)).initCause(e);
 					}
@@ -484,6 +528,12 @@ public final class RestContext extends Context {
 		}
 	}
 
+	private static void addToRouter(Map<String, CallRouter.Builder> routers, String httpMethodName, CallMethod cm) throws RestServletException {
+		if (! routers.containsKey(httpMethodName))
+			routers.put(httpMethodName, new CallRouter.Builder(httpMethodName));
+		routers.get(httpMethodName).add(cm);
+	}
+
 	private static class Builder {
 
 		boolean allowHeaderParams, allowBodyParam, renderResponseStackTraces, useStackTraceHashes;
@@ -1416,5 +1466,4 @@ public final class RestContext extends Context {
 			throw new RestServletException("Exception occurred while constructing class ''{0}''", c).initCause(e);
 		}
 	}
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
index 2b55b47..8a13d2f 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
@@ -148,7 +148,8 @@ public abstract class RestLogger {
 	 * <p>
 	 * Subclasses can override this method to provide their own logic for determining when exceptions are logged.
 	 * <p>
-	 * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string.
+	 * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string
+	 * 	or <code>No-Trace: true</code> is specified in the header.
 	 *
 	 * @param req The HTTP request.
 	 * @param res The HTTP response.
@@ -156,6 +157,8 @@ public abstract class RestLogger {
 	 * @return <jk>true</jk> if exception should be logged.
 	 */
 	protected boolean shouldLog(HttpServletRequest req, HttpServletResponse res, RestException e) {
+		if ("true".equals(req.getHeader("No-Trace")))
+			return false;
 		String q = req.getQueryString();
 		return (q == null ? true : q.indexOf("noTrace=true") == -1);
 	}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
index 98aba93..cd5d42c 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -67,7 +67,8 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	private final RestContext context;
 
 	private final String method;
-	private String pathRemainder, body;
+	private String pathRemainder;
+	private byte[] body;
 	private Method javaMethod;
 	private ObjectMap properties;
 	private SerializerGroup serializerGroup;
@@ -118,14 +119,16 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			method = _method;
 
 			if (context.isAllowBodyParam()) {
-				body = getQueryParameter("body");
-				if (body != null)
+				String b = getQueryParameter("body");
+				if (b != null) {
 					setHeader("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
+					this.body = b.getBytes(IOUtils.UTF8);
+				}
 			}
 
 			defaultServletHeaders = context.getDefaultRequestHeaders();
 
-			debug = "true".equals(getQueryParameter("debug", "false"));
+			debug = "true".equals(getQueryParameter("debug", "false")) || "true".equals(getHeader("Debug", "false"));
 
 			if (debug) {
 				context.getLogger().log(Level.INFO, toString());
@@ -1216,10 +1219,9 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @throws IOException If a problem occurred trying to read from the reader.
 	 */
 	public String getBodyAsString() throws IOException {
-		if (body != null)
-			return body;
-		body = IOUtils.read(getReader()).toString();
-		return body;
+		if (body == null)
+			body = IOUtils.readBytes(getInputStream(), 1024);
+		return new String(body, IOUtils.UTF8);
 	}
 
 	/**
@@ -1247,7 +1249,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 */
 	protected Reader getUnbufferedReader() throws IOException {
 		if (body != null)
-			return new CharSequenceReader(body);
+			return new CharSequenceReader(new String(body, IOUtils.UTF8));
 		return new InputStreamReader(getInputStream(), getCharacterEncoding());
 	}
 
@@ -1263,21 +1265,15 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	@Override /* ServletRequest */
 	public ServletInputStream getInputStream() throws IOException {
 
+		if (body != null)
+			return new ServletInputStream2(body);
+
 		Encoder enc = getEncoder();
 
 		ServletInputStream is = super.getInputStream();
 		if (enc != null) {
 			final InputStream is2 = enc.getInputStream(is);
-			return new ServletInputStream() {
-				@Override /* InputStream */
-				public final int read() throws IOException {
-					return is2.read();
-				}
-				@Override /* InputStream */
-				public final void close() throws IOException {
-					is2.close();
-				}
-			};
+			return new ServletInputStream2(is2);
 		}
 		return is;
 	}
@@ -1893,7 +1889,9 @@ public final class RestRequest extends HttpServletRequestWrapper {
 		for (Map.Entry<String,String> e : defaultServletHeaders.entrySet()) {
 			sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n");
 		}
-		if (method.equals("PUT") || method.equals("POST")) {
+		if (javaMethod == null) {
+			sb.append("***init() not called yet!***\n");
+		} else if (method.equals("PUT") || method.equals("POST")) {
 			sb.append("---Body---\n");
 			try {
 				sb.append(getBodyAsString()).append("\n");
@@ -1985,4 +1983,30 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	void setJavaMethod(Method method) {
 		this.javaMethod = method;
 	}
+
+	/**
+	 * ServletInputStream wrapper around a normal input stream.
+	 */
+	private static class ServletInputStream2 extends ServletInputStream {
+
+		private final InputStream is;
+
+		private ServletInputStream2(InputStream is) {
+			this.is = is;
+		}
+
+		private ServletInputStream2(byte[] b) {
+			this(new ByteArrayInputStream(b));
+		}
+
+		@Override /* InputStream */
+		public final int read() throws IOException {
+			return is.read();
+		}
+
+		@Override /* InputStream */
+		public final void close() throws IOException {
+			is.close();
+		}
+	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
index 4856070..4270a38 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
@@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.lang.annotation.*;
 
+import org.apache.juneau.annotation.*;
 import org.apache.juneau.encoders.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.*;
@@ -38,13 +39,26 @@ public @interface RestMethod {
 	 * <p>
 	 * Typically <js>"GET"</js>, <js>"PUT"</js>, <js>"POST"</js>, <js>"DELETE"</js>, or <js>"OPTIONS"</js>.
 	 * <p>
-	 * Can also be a non-HTTP-standard name that is passed in through a <code>&amp;method=methodName</code> URL parameter.
-	 * <p>
 	 * Method names are case-insensitive (always folded to upper-case).
 	 * <p>
-	 * If a method name is not specified, then the method name is determined based on the Java method name.<br>
-	 * 	For example, if the method is <code>doPost(...)</code>, then the method name is automatically detected as <js>"POST"</js>.
-
+	 * Besides the standard HTTP method names, the following can also be specified:
+	 * <ul>
+	 * 	<li><js>"*"</js> - Denotes any method.
+	 * 		<br>Use this if you want to capture any HTTP methods in a single Java method.
+	 * 		<br>The {@link Method @Method} annotation and/or {@link RestRequest#getMethod()} method can be used
+	 * 		to distinguish the actual HTTP method name.
+	 * 	<li><js>""</js> - Auto-detect.
+	 * 		<br>The method name is determined based on the Java method name.
+	 * 		<br>For example, if the method is <code>doPost(...)</code>, then the method name is automatically detected as <js>"POST"</js>.
+	 * 	<li><js>"PROXY"</js> - Remote-proxy interface.
+	 * 		<br>This denotes a Java method that returns an object (usually an interface, often annotated with the {@link Remoteable @Remoteable} annotation)
+	 * 		to be used as a remote proxy using <code>RestClient.getRemoteableProxy(Class<T> interfaceClass, String url)</code>.
+	 * 		<br>This allows you to construct client-side interface proxies using REST as a transport medium.
+	 * 		<br>Conceptually, this is simply a fancy <code>POST</code> against the url <js>"/{path}/{javaMethodName}"</js> where the arguments
+	 * 		are marshalled from the client to the server as an HTTP body containing an array of objects,
+	 * 		passed to the method as arguments, and then the resulting object is marshalled back to the client.
+	 * 	<li>Anything else - Overloaded non-HTTP-standard names that are passed in through a <code>&amp;method=methodName</code> URL parameter.
+	 * </ul>
 	 */
 	String name() default "";
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
index 15a96ad..c6bc0f4 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/remoteable/RemoteableServlet.java
@@ -115,7 +115,7 @@ public abstract class RemoteableServlet extends RestServletDefault {
 			throw new RestException(SC_NOT_FOUND, "Method not found"); //$NON-NLS-1$
 
 		// Parse the args and invoke the method.
-		ClassMeta<?>[] argTypes = req.getBeanSession().getClassMetas(m.getParameterTypes());
+		ClassMeta<?>[] argTypes = req.getBeanSession().getClassMetas(m.getGenericParameterTypes());
 		Object[] params = p.parseArgs(req.getReader(), argTypes);
 		return m.invoke(service, params);
 	}


[2/2] incubator-juneau git commit: Add support for @RestMethod proxies.

Posted by ja...@apache.org.
Add support for @RestMethod proxies.

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

Branch: refs/heads/master
Commit: 9db2e03fa04ec54e5e2d85f9c4ab530233700853
Parents: 1174420
Author: JamesBognar <ja...@apache.org>
Authored: Sat Mar 25 10:25:20 2017 -0700
Committer: JamesBognar <ja...@apache.org>
Committed: Sat Mar 25 10:25:20 2017 -0700

----------------------------------------------------------------------
 .../a/rttests/RoundTripLargeObjectsTest.java    |  18 +-
 .../ByteArrayBase64SwapComboTest.java           |  28 +--
 .../transforms/CalendarSwapComboTest.java       |  72 +++---
 .../juneau/transforms/DateSwapComboTest.java    |  72 +++---
 .../urlencoding/UrlEncodingSerializerTest.java  |  16 +-
 .../java/org/apache/juneau/BeanSession.java     |   8 +-
 .../main/java/org/apache/juneau/ClassMeta.java  |  33 ++-
 .../java/org/apache/juneau/html/HtmlParser.java |  40 +++-
 .../java/org/apache/juneau/html/HtmlTag.java    |  10 +-
 .../org/apache/juneau/internal/ClassUtils.java  |  41 ++++
 .../apache/juneau/msgpack/MsgPackParser.java    |  25 +++
 .../java/org/apache/juneau/parser/Parser.java   |  30 ++-
 .../juneau/parser/ParserGroupBuilder.java       |  12 +
 .../serializer/SerializerGroupBuilder.java      |  12 +
 .../java/org/apache/juneau/uon/UonParser.java   |  14 +-
 .../juneau/urlencoding/UrlEncodingParser.java   |  84 +++++--
 .../urlencoding/UrlEncodingSerializer.java      |  45 +++-
 juneau-core/src/main/javadoc/overview.html      |  35 +++
 .../examples/addressbook/AddressBook.java       |  25 ++-
 .../examples/addressbook/IAddressBook.java      |   3 +
 .../rest/addressbook/AddressBookResource.java   |  29 +--
 .../examples/rest/AddressBookResourceTest.java  |  40 ++++
 .../org/apache/juneau/rest/client/RestCall.java | 153 +++++++++----
 .../apache/juneau/rest/client/RestClient.java   |  74 ++++--
 .../juneau/rest/client/RestClientBuilder.java   |  15 ++
 juneau-rest-test/pom.xml                        |   1 +
 .../apache/juneau/rest/test/InterfaceProxy.java |  62 +++++
 .../rest/test/InterfaceProxyResource.java       | 128 +++++++++++
 .../java/org/apache/juneau/rest/test/Root.java  |   1 +
 .../juneau/rest/test/InterfaceProxyTest.java    | 225 +++++++++++++++++++
 .../apache/juneau/rest/test/RestTestcase.java   |  24 ++
 .../juneau/rest/test/TestMicroservice.java      |   2 +-
 .../org/apache/juneau/rest/test/TestUtils.java  |   7 +
 .../org/apache/juneau/rest/test/_TestSuite.java |   1 +
 .../java/org/apache/juneau/rest/CallMethod.java |  14 +-
 .../org/apache/juneau/rest/RestContext.java     |  63 +++++-
 .../java/org/apache/juneau/rest/RestLogger.java |   5 +-
 .../org/apache/juneau/rest/RestRequest.java     |  64 ++++--
 .../juneau/rest/annotation/RestMethod.java      |  24 +-
 .../rest/remoteable/RemoteableServlet.java      |   2 +-
 40 files changed, 1295 insertions(+), 262 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjectsTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjectsTest.java b/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjectsTest.java
index 4e41a60..139f03c 100755
--- a/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjectsTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/a/rttests/RoundTripLargeObjectsTest.java
@@ -50,55 +50,55 @@ public class RoundTripLargeObjectsTest extends RoundTripTest {
 			{ /* 0 */
 				"Json DEFAULT",
 				new JsonSerializerBuilder().trimNullProperties(false),
-				JsonParser.DEFAULT,
+				new JsonParserBuilder(),
 				0
 			},
 			{ /* 1 */
 				"Json DEFAULT_LAX",
 				new JsonSerializerBuilder().simple().trimNullProperties(false),
-				JsonParser.DEFAULT,
+				new JsonParserBuilder(),
 				0
 			},
 			{ /* 2 */
 				"Json DEFAULT_SQ",
 				new JsonSerializerBuilder().simple().trimNullProperties(false),
-				JsonParser.DEFAULT,
+				new JsonParserBuilder(),
 				0
 			},
 			{ /* 3 */
 				"Xml DEFAULT w/namespaces,validation",
 				new XmlSerializerBuilder().sq().ns().trimNullProperties(false).addNamespaceUrisToRoot(true).useWhitespace(true),
-				XmlParser.DEFAULT,
+				new XmlParserBuilder(),
 				CHECK_XML_WHITESPACE | VALIDATE_XML
 			},
 			{ /* 4 */
 				"Xml DEFAULT wo/namespaces,validation",
 				new XmlSerializerBuilder().sq().trimNullProperties(false),
-				XmlParser.DEFAULT,
+				new XmlParserBuilder(),
 				CHECK_XML_WHITESPACE
 			},
 			{ /* 5 */
 				"Html",
 				new HtmlSerializerBuilder().trimNullProperties(false),
-				HtmlParser.DEFAULT,
+				new HtmlParserBuilder(),
 				CHECK_XML_WHITESPACE
 			},
 			{ /* 6 */
 				"UrlEncoding",
 				new UrlEncodingSerializerBuilder().trimNullProperties(false),
-				UrlEncodingParser.DEFAULT,
+				new UrlEncodingParserBuilder(),
 				0
 			},
 			{ /* 7 */
 				"Uon",
 				new UonSerializerBuilder().trimNullProperties(false),
-				UonParser.DEFAULT,
+				new UonParserBuilder(),
 				0
 			},
 			{ /* 8 */
 				"MsgPack",
 				new MsgPackSerializerBuilder().trimNullProperties(false),
-				MsgPackParser.DEFAULT,
+				new MsgPackParserBuilder(),
 				0
 			},
 //			{ /* 9 */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core-test/src/test/java/org/apache/juneau/transforms/ByteArrayBase64SwapComboTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/transforms/ByteArrayBase64SwapComboTest.java b/juneau-core-test/src/test/java/org/apache/juneau/transforms/ByteArrayBase64SwapComboTest.java
index b9ac9c3..08934de 100644
--- a/juneau-core-test/src/test/java/org/apache/juneau/transforms/ByteArrayBase64SwapComboTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/transforms/ByteArrayBase64SwapComboTest.java
@@ -30,7 +30,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 	@Parameterized.Parameters
 	public static Collection<Object[]> getParameters() {
 		return Arrays.asList(new Object[][] {
-			{
+			{ 	/* 0 */
 				"ByteArray1d",
 				new byte[] {1,2,3},
 				/* Json */		"'AQID'",
@@ -55,7 +55,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>AQID</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>AQID</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{ 	/* 1 */
 				"ByteArray2d",
 				new byte[][]{{1,2,3},{4,5,6},null},
 				/* Json */		"['AQID','BAUG',null]",
@@ -71,16 +71,16 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* Uon */		"@(AQID,BAUG,null)",
 				/* UonT */		"@(AQID,BAUG,null)",
 				/* UonR */		"@(\n\tAQID,\n\tBAUG,\n\tnull\n)",
-				/* UrlEnc */	"_value=@(AQID,BAUG,null)",
-				/* UrlEncT */	"_value=@(AQID,BAUG,null)",
-				/* UrlEncR */	"_value=@(\n\tAQID,\n\tBAUG,\n\tnull\n)",
+				/* UrlEnc */	"0=AQID&1=BAUG&2=null",
+				/* UrlEncT */	"0=AQID&1=BAUG&2=null",
+				/* UrlEncR */	"0=AQID\n&1=BAUG\n&2=null",
 				/* MsgPack */	"93A441514944A442415547C0",
 				/* MsgPackT */	"93A441514944A442415547C0",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>AQID</rdf:li>\n    <rdf:li>BAUG</rdf:li>\n    <rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{ 	/* 2 */
 				"ListOfByteArrays",
 				new ArrayList<byte[]>(){{
 					add(new byte[]{1,2,3});
@@ -109,7 +109,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>AQID</rdf:li>\n    <rdf:li>BAUG</rdf:li>\n    <rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{ 	/* 3 */
 				"MapOfByteArrays",
 				new LinkedHashMap<String,byte[]>() {{
 					put("foo", new byte[]{1,2,3});
@@ -139,7 +139,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>AQID</jp:foo>\n<jp:bar rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n<jp:_x0000_>BAUG</jp:_x0000_>\n<jp:null>BwgJ</jp:null>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>AQID</jp:foo>\n    <jp:bar rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n    <jp:_x0000_>BAUG</jp:_x0000_>\n    <jp:null>BwgJ</jp:null>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{ 	/* 4 */
 				"BeanWithByteArrayField",
 				new BeanWithByteArrayField().init(),
 				/* Json */		"{f:'AQID'}",
@@ -164,7 +164,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:f>AQID</jp:f>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:f>AQID</jp:f>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 5 */
 				"BeanWithByteArray2dField",
 				new BeanWithByteArray2dField().init(),
 				/* Json */		"{f:['AQID','BAUG',null]}",
@@ -189,7 +189,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:f>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</jp:f>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:f>\n      <rdf:Seq>\n        <rdf:li>AQID</rdf:li>\n        <rdf:li>BAUG</rdf:li>\n        <rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n      </rdf:Seq>\n    </jp:f>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 6 */
 				"BeanWithByteArrayNullField",
 				new BeanWithByteArrayNullField().init(),
 				/* Json */		"{f:null}",
@@ -214,7 +214,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:f rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:f rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 7 */
 				"BeanWithByteArrayListField",
 				new BeanWithByteArrayListField().init(),
 				/* Json */		"{f:['AQID','BAUG',null]}",
@@ -239,7 +239,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:f>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</jp:f>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:f>\n      <rdf:Seq>\n        <rdf:li>AQID</rdf:li>\n        <rdf:li>BAUG</rdf:li>\n        <rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n      </rdf:Seq>\n    </jp:f>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 8 */
 				"BeanWithByteArrayMapField",
 				new BeanWithByteArrayMapField().init(),
 				/* Json */		"{f:{foo:'AQID',bar:null,null:'BAUG'}}",
@@ -264,7 +264,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:f rdf:parseType='Resource'>\n<jp:foo>AQID</jp:foo>\n<jp:bar rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n<jp:_x0000_>BAUG</jp:_x0000_>\n</jp:f>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:f rdf:parseType='Resource'>\n      <jp:foo>AQID</jp:foo>\n      <jp:bar rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n      <jp:_x0000_>BAUG</jp:_x0000_>\n    </jp:f>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 9 */
 				"BeanWithByteArrayBeanListField",
 				new BeanWithByteArrayBeanListField().init(),
 				/* Json */		"{f:[{f1:'AQID',f2:['AQID','BAUG',null],f3:null,f4:['AQID','BAUG',null],f5:{foo:'AQID',bar:null,null:'BAUG'}},null]}",
@@ -289,7 +289,7 @@ public class ByteArrayBase64SwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:f>\n<rdf:Seq>\n<rdf:li rdf:parseType='Resource'>\n<jp:f1>AQID</jp:f1>\n<jp:f2>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</jp:f2>\n<jp:f3 rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n<jp:f4>\n<rdf:Seq>\n<rdf:li>AQID</rdf:li>\n<rdf:li>BAUG</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</jp:f4>\n<jp:f5 rdf:parseType='Resource'>\n<jp:foo>AQID</jp:foo>\n<jp:bar rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n<jp:_x0000_>BAUG</jp:_x0000_>\n</jp:f5>\n</rdf:li>\n<rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n</rdf:Seq>\n</jp:f>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:f>\n      <rdf:Seq>\n        <rdf:li rdf:parseType='Resource'>\n          <jp:f1>AQID</jp:f1>\n          <jp:f2>\n            <rdf:Seq>\n              <rdf:li>AQID</rdf:li>\n              <rdf:li>BAUG</rdf:li>\n              <rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n            </rdf:Seq>\n          </jp:f2>\n          <jp:f3 rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n          <jp:f4>\n            <rdf:Seq>\n              <rdf:li>AQID</rdf:li>\n              <rdf:li>BAUG</rdf:li>\n              <rdf:li rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n            </rdf:Seq>\n          </jp:f4>\n          <jp:f5 rdf:parseType='Resource'>\n            <jp:foo>AQID</jp:foo>\n            <jp:bar rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n            <jp:_x0000_>BAUG</jp:_x0000_>\n          </jp:f5>\n        </rdf:li>\n        <rdf:li rdf:r
 esource='http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'/>\n      </rdf:Seq>\n    </jp:f>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 10 */
 				"BeanWithByteArrayBeanMapField",
 				new BeanWithByteArrayBeanMapField().init(),
 				/* Json */		"{f:{foo:{f1:'AQID',f2:['AQID','BAUG',null],f3:null,f4:['AQID','BAUG',null],f5:{foo:'AQID',bar:null,null:'BAUG'}},bar:null,null:{f1:'AQID',f2:['AQID','BAUG',null],f3:null,f4:['AQID','BAUG',null],f5:{foo:'AQID',bar:null,null:'BAUG'}}}}",

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core-test/src/test/java/org/apache/juneau/transforms/CalendarSwapComboTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/transforms/CalendarSwapComboTest.java b/juneau-core-test/src/test/java/org/apache/juneau/transforms/CalendarSwapComboTest.java
index c3453b3..b804241 100644
--- a/juneau-core-test/src/test/java/org/apache/juneau/transforms/CalendarSwapComboTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/transforms/CalendarSwapComboTest.java
@@ -42,7 +42,7 @@ public class CalendarSwapComboTest extends ComboTest {
 	@Parameterized.Parameters
 	public static Collection<Object[]> getParameters() {
 		return Arrays.asList(new Object[][] {
-			{
+			{	/* 0 */
 				"CalendarSwap.ToString/singleDate",
 				singleDate,
 				CalendarSwap.ToString.class,
@@ -68,7 +68,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>Sun Mar 03 10:11:12 PST 1901</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>Sun Mar 03 10:11:12 PST 1901</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 1 */
 				"CalendarSwap.ToString/dateArray",
 				dateArray,
 				CalendarSwap.ToString.class,
@@ -85,16 +85,16 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* Uon */		"@('Sun Mar 03 10:11:12 PST 1901')",
 				/* UonT */		"@('Sun Mar 03 10:11:12 PST 1901')",
 				/* UonR */		"@(\n\t'Sun Mar 03 10:11:12 PST 1901'\n)",
-				/* UrlEnc */	"_value=@('Sun+Mar+03+10:11:12+PST+1901')",
-				/* UrlEncT */	"_value=@('Sun+Mar+03+10:11:12+PST+1901')",
-				/* UrlEncR */	"_value=@(\n\t'Sun+Mar+03+10:11:12+PST+1901'\n)",
+				/* UrlEnc */	"0='Sun+Mar+03+10:11:12+PST+1901'",
+				/* UrlEncT */	"0='Sun+Mar+03+10:11:12+PST+1901'",
+				/* UrlEncR */	"0='Sun+Mar+03+10:11:12+PST+1901'",
 				/* MsgPack */	"91BC53756E204D61722030332031303A31313A3132205053542031393031",
 				/* MsgPackT */	"91BC53756E204D61722030332031303A31313A3132205053542031393031",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun Mar 03 10:11:12 PST 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun Mar 03 10:11:12 PST 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>Sun Mar 03 10:11:12 PST 1901</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 2 */
 				"CalendarSwap.ToString",
 				dateMap,
 				CalendarSwap.ToString.class,
@@ -120,7 +120,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>Sun Mar 03 10:11:12 PST 1901</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>Sun Mar 03 10:11:12 PST 1901</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 3 */
 				"CalendarSwap.ISO8601DT/singleDate",
 				singleDate,
 				CalendarSwap.ISO8601DT.class,
@@ -146,7 +146,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>1901-03-03T10:11:12-08:00</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>1901-03-03T10:11:12-08:00</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 4 */
 				"CalendarSwap.ISO8601DT/dateArray",
 				dateArray,
 				CalendarSwap.ISO8601DT.class,
@@ -163,16 +163,16 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* Uon */		"@(1901-03-03T10:11:12-08:00)",
 				/* UonT */		"@(1901-03-03T10:11:12-08:00)",
 				/* UonR */		"@(\n\t1901-03-03T10:11:12-08:00\n)",
-				/* UrlEnc */	"_value=@(1901-03-03T10:11:12-08:00)",
-				/* UrlEncT */	"_value=@(1901-03-03T10:11:12-08:00)",
-				/* UrlEncR */	"_value=@(\n\t1901-03-03T10:11:12-08:00\n)",
+				/* UrlEnc */	"0=1901-03-03T10:11:12-08:00",
+				/* UrlEncT */	"0=1901-03-03T10:11:12-08:00",
+				/* UrlEncR */	"0=1901-03-03T10:11:12-08:00",
 				/* MsgPack */	"91B9313930312D30332D30335431303A31313A31322D30383A3030",
 				/* MsgPackT */	"91B9313930312D30332D30335431303A31313A31322D30383A3030",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>1901-03-03T10:11:12-08:00</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>1901-03-03T10:11:12-08:00</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>1901-03-03T10:11:12-08:00</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 5 */
 				"CalendarSwap.ISO8601DT/dateMap",
 				dateMap,
 				CalendarSwap.ISO8601DT.class,
@@ -198,7 +198,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>1901-03-03T10:11:12-08:00</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>1901-03-03T10:11:12-08:00</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 6 */
 				"CalendarSwap.RFC2822DTZ/singleDate",
 				singleDate,
 				CalendarSwap.RFC2822DTZ.class,
@@ -224,7 +224,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>Sun, 03 Mar 1901 18:11:12 GMT</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>Sun, 03 Mar 1901 18:11:12 GMT</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 7 */
 				"CalendarSwap.RFC2822DTZ/dateArray",
 				dateArray,
 				CalendarSwap.RFC2822DTZ.class,
@@ -241,16 +241,16 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* Uon */		"@('Sun, 03 Mar 1901 18:11:12 GMT')",
 				/* UonT */		"@('Sun, 03 Mar 1901 18:11:12 GMT')",
 				/* UonR */		"@(\n\t'Sun, 03 Mar 1901 18:11:12 GMT'\n)",
-				/* UrlEnc */	"_value=@('Sun,+03+Mar+1901+18:11:12+GMT')",
-				/* UrlEncT */	"_value=@('Sun,+03+Mar+1901+18:11:12+GMT')",
-				/* UrlEncR */	"_value=@(\n\t'Sun,+03+Mar+1901+18:11:12+GMT'\n)",
+				/* UrlEnc */	"0='Sun,+03+Mar+1901+18:11:12+GMT'",
+				/* UrlEncT */	"0='Sun,+03+Mar+1901+18:11:12+GMT'",
+				/* UrlEncR */	"0='Sun,+03+Mar+1901+18:11:12+GMT'",
 				/* MsgPack */	"91BD53756E2C203033204D617220313930312031383A31313A313220474D54",
 				/* MsgPackT */	"91BD53756E2C203033204D617220313930312031383A31313A313220474D54",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun, 03 Mar 1901 18:11:12 GMT</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun, 03 Mar 1901 18:11:12 GMT</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>Sun, 03 Mar 1901 18:11:12 GMT</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 8 */
 				"CalendarSwap.RFC2822DTZ/dateMap",
 				dateMap,
 				CalendarSwap.RFC2822DTZ.class,
@@ -276,7 +276,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>Sun, 03 Mar 1901 18:11:12 GMT</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>Sun, 03 Mar 1901 18:11:12 GMT</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 9 */
 				"CalendarLongSwap",
 				singleDate,
 				CalendarLongSwap.class,
@@ -302,7 +302,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>-2172116928000</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>-2172116928000</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 10 */
 				"CalendarLongSwap/dateArray",
 				dateArray,
 				CalendarLongSwap.class,
@@ -319,16 +319,16 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* Uon */		"@(-2172116928000)",
 				/* UonT */		"@(-2172116928000)",
 				/* UonR */		"@(\n\t-2172116928000\n)",
-				/* UrlEnc */	"_value=@(-2172116928000)",
-				/* UrlEncT */	"_value=@(-2172116928000)",
-				/* UrlEncR */	"_value=@(\n\t-2172116928000\n)",
+				/* UrlEnc */	"0=-2172116928000",
+				/* UrlEncT */	"0=-2172116928000",
+				/* UrlEncR */	"0=-2172116928000",
 				/* MsgPack */	"91D3FFFFFE0643BDFA00",
 				/* MsgPackT */	"91D3FFFFFE0643BDFA00",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>-2172116928000</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>-2172116928000</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>-2172116928000</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 11 */
 				"CalendarLongSwap/dateMap",
 				dateMap,
 				CalendarLongSwap.class,
@@ -354,7 +354,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>-2172116928000</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>-2172116928000</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 12 */
 				"CalendarMapSwap/singleDate",
 				singleDate,
 				CalendarMapSwap.class,
@@ -380,7 +380,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:time>-2172116928000</jp:time>\n<jp:timeZone>PST</jp:timeZone>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:time>-2172116928000</jp:time>\n    <jp:timeZone>PST</jp:timeZone>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 13 */
 				"CalendarMapSwap/dateArray",
 				dateArray,
 				CalendarMapSwap.class,
@@ -397,16 +397,16 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* Uon */		"@((time=-2172116928000,timeZone=PST))",
 				/* UonT */		"@((time=-2172116928000,timeZone=PST))",
 				/* UonR */		"@(\n\t(\n\t\ttime=-2172116928000,\n\t\ttimeZone=PST\n\t)\n)",
-				/* UrlEnc */	"_value=@((time=-2172116928000,timeZone=PST))",
-				/* UrlEncT */	"_value=@((time=-2172116928000,timeZone=PST))",
-				/* UrlEncR */	"_value=@(\n\t(\n\t\ttime=-2172116928000,\n\t\ttimeZone=PST\n\t)\n)",
+				/* UrlEnc */	"0=(time=-2172116928000,timeZone=PST)",
+				/* UrlEncT */	"0=(time=-2172116928000,timeZone=PST)",
+				/* UrlEncR */	"0=(\n\ttime=-2172116928000,\n\ttimeZone=PST\n)",
 				/* MsgPack */	"9182A474696D65D3FFFFFE0643BDFA00A874696D655A6F6E65A3505354",
 				/* MsgPackT */	"9182A474696D65D3FFFFFE0643BDFA00A874696D655A6F6E65A3505354",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li rdf:parseType='Resource'>\n<jp:time>-2172116928000</jp:time>\n<jp:timeZone>PST</jp:timeZone>\n</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li rdf:parseType='Resource'>\n<jp:time>-2172116928000</jp:time>\n<jp:timeZone>PST</jp:timeZone>\n</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li rdf:parseType='Resource'>\n      <jp:time>-2172116928000</jp:time>\n      <jp:timeZone>PST</jp:timeZone>\n    </rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 14 */
 				"CalendarMapSwap/dateMap",
 				dateMap,
 				CalendarMapSwap.class,
@@ -432,7 +432,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo rdf:parseType='Resource'>\n<jp:time>-2172116928000</jp:time>\n<jp:timeZone>PST</jp:timeZone>\n</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo rdf:parseType='Resource'>\n      <jp:time>-2172116928000</jp:time>\n      <jp:timeZone>PST</jp:timeZone>\n    </jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 15 */
 				"CalendarSwap.DateMedium/singleDate",
 				singleDate,
 				CalendarSwap.DateMedium.class,
@@ -458,7 +458,7 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>Mar 3, 1901</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>Mar 3, 1901</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 16 */
 				"CalendarSwap.DateMedium/dateArray",
 				dateArray,
 				CalendarSwap.DateMedium.class,
@@ -475,16 +475,16 @@ public class CalendarSwapComboTest extends ComboTest {
 				/* Uon */		"@('Mar 3, 1901')",
 				/* UonT */		"@('Mar 3, 1901')",
 				/* UonR */		"@(\n\t'Mar 3, 1901'\n)",
-				/* UrlEnc */	"_value=@('Mar+3,+1901')",
-				/* UrlEncT */	"_value=@('Mar+3,+1901')",
-				/* UrlEncR */	"_value=@(\n\t'Mar+3,+1901'\n)",
+				/* UrlEnc */	"0='Mar+3,+1901'",
+				/* UrlEncT */	"0='Mar+3,+1901'",
+				/* UrlEncR */	"0='Mar+3,+1901'",
 				/* MsgPack */	"91AB4D617220332C2031393031",
 				/* MsgPackT */	"91AB4D617220332C2031393031",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Mar 3, 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Mar 3, 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>Mar 3, 1901</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 17 */
 				"CalendarSwap.DateMedium/dateMap",
 				dateMap,
 				CalendarSwap.DateMedium.class,

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core-test/src/test/java/org/apache/juneau/transforms/DateSwapComboTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/transforms/DateSwapComboTest.java b/juneau-core-test/src/test/java/org/apache/juneau/transforms/DateSwapComboTest.java
index 5965e94..376cbec 100644
--- a/juneau-core-test/src/test/java/org/apache/juneau/transforms/DateSwapComboTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/transforms/DateSwapComboTest.java
@@ -38,7 +38,7 @@ public class DateSwapComboTest extends ComboTest {
 	@Parameterized.Parameters
 	public static Collection<Object[]> getParameters() {
 		return Arrays.asList(new Object[][] {
-			{
+			{	/* 0 */
 				"DateSwap.ToString/singleDate",
 				singleDate,
 				DateSwap.ToString.class,
@@ -64,7 +64,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>Sun Mar 03 10:11:12 PST 1901</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>Sun Mar 03 10:11:12 PST 1901</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 1 */
 				"DateSwap.ToString/dateArray",
 				dateArray,
 				DateSwap.ToString.class,
@@ -81,16 +81,16 @@ public class DateSwapComboTest extends ComboTest {
 				/* Uon */		"@('Sun Mar 03 10:11:12 PST 1901')",
 				/* UonT */		"@('Sun Mar 03 10:11:12 PST 1901')",
 				/* UonR */		"@(\n\t'Sun Mar 03 10:11:12 PST 1901'\n)",
-				/* UrlEnc */	"_value=@('Sun+Mar+03+10:11:12+PST+1901')",
-				/* UrlEncT */	"_value=@('Sun+Mar+03+10:11:12+PST+1901')",
-				/* UrlEncR */	"_value=@(\n\t'Sun+Mar+03+10:11:12+PST+1901'\n)",
+				/* UrlEnc */	"0='Sun+Mar+03+10:11:12+PST+1901'",
+				/* UrlEncT */	"0='Sun+Mar+03+10:11:12+PST+1901'",
+				/* UrlEncR */	"0='Sun+Mar+03+10:11:12+PST+1901'",
 				/* MsgPack */	"91BC53756E204D61722030332031303A31313A3132205053542031393031",
 				/* MsgPackT */	"91BC53756E204D61722030332031303A31313A3132205053542031393031",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun Mar 03 10:11:12 PST 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun Mar 03 10:11:12 PST 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>Sun Mar 03 10:11:12 PST 1901</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 2 */
 				"DateSwap.ToString",
 				dateMap,
 				DateSwap.ToString.class,
@@ -116,7 +116,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>Sun Mar 03 10:11:12 PST 1901</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>Sun Mar 03 10:11:12 PST 1901</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 3 */
 				"DateSwap.ISO8601DT/singleDate",
 				singleDate,
 				DateSwap.ISO8601DT.class,
@@ -142,7 +142,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>1901-03-03T10:11:12-08:00</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>1901-03-03T10:11:12-08:00</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 4 */
 				"DateSwap.ISO8601DT/dateArray",
 				dateArray,
 				DateSwap.ISO8601DT.class,
@@ -159,16 +159,16 @@ public class DateSwapComboTest extends ComboTest {
 				/* Uon */		"@(1901-03-03T10:11:12-08:00)",
 				/* UonT */		"@(1901-03-03T10:11:12-08:00)",
 				/* UonR */		"@(\n\t1901-03-03T10:11:12-08:00\n)",
-				/* UrlEnc */	"_value=@(1901-03-03T10:11:12-08:00)",
-				/* UrlEncT */	"_value=@(1901-03-03T10:11:12-08:00)",
-				/* UrlEncR */	"_value=@(\n\t1901-03-03T10:11:12-08:00\n)",
+				/* UrlEnc */	"0=1901-03-03T10:11:12-08:00",
+				/* UrlEncT */	"0=1901-03-03T10:11:12-08:00",
+				/* UrlEncR */	"0=1901-03-03T10:11:12-08:00",
 				/* MsgPack */	"91B9313930312D30332D30335431303A31313A31322D30383A3030",
 				/* MsgPackT */	"91B9313930312D30332D30335431303A31313A31322D30383A3030",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>1901-03-03T10:11:12-08:00</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>1901-03-03T10:11:12-08:00</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>1901-03-03T10:11:12-08:00</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 5 */
 				"DateSwap.ISO8601DT/dateMap",
 				dateMap,
 				DateSwap.ISO8601DT.class,
@@ -194,7 +194,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>1901-03-03T10:11:12-08:00</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>1901-03-03T10:11:12-08:00</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 6 */
 				"DateSwap.RFC2822DTZ/singleDate",
 				singleDate,
 				DateSwap.RFC2822DTZ.class,
@@ -220,7 +220,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>Sun, 03 Mar 1901 18:11:12 GMT</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>Sun, 03 Mar 1901 18:11:12 GMT</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 7 */
 				"DateSwap.RFC2822DTZ/dateArray",
 				dateArray,
 				DateSwap.RFC2822DTZ.class,
@@ -237,16 +237,16 @@ public class DateSwapComboTest extends ComboTest {
 				/* Uon */		"@('Sun, 03 Mar 1901 18:11:12 GMT')",
 				/* UonT */		"@('Sun, 03 Mar 1901 18:11:12 GMT')",
 				/* UonR */		"@(\n\t'Sun, 03 Mar 1901 18:11:12 GMT'\n)",
-				/* UrlEnc */	"_value=@('Sun,+03+Mar+1901+18:11:12+GMT')",
-				/* UrlEncT */	"_value=@('Sun,+03+Mar+1901+18:11:12+GMT')",
-				/* UrlEncR */	"_value=@(\n\t'Sun,+03+Mar+1901+18:11:12+GMT'\n)",
+				/* UrlEnc */	"0='Sun,+03+Mar+1901+18:11:12+GMT'",
+				/* UrlEncT */	"0='Sun,+03+Mar+1901+18:11:12+GMT'",
+				/* UrlEncR */	"0='Sun,+03+Mar+1901+18:11:12+GMT'",
 				/* MsgPack */	"91BD53756E2C203033204D617220313930312031383A31313A313220474D54",
 				/* MsgPackT */	"91BD53756E2C203033204D617220313930312031383A31313A313220474D54",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun, 03 Mar 1901 18:11:12 GMT</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Sun, 03 Mar 1901 18:11:12 GMT</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>Sun, 03 Mar 1901 18:11:12 GMT</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 8 */
 				"DateSwap.RFC2822DTZ/dateMap",
 				dateMap,
 				DateSwap.RFC2822DTZ.class,
@@ -272,7 +272,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>Sun, 03 Mar 1901 18:11:12 GMT</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>Sun, 03 Mar 1901 18:11:12 GMT</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 9 */
 				"DateLongSwap",
 				singleDate,
 				DateLongSwap.class,
@@ -298,7 +298,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>-2172116928000</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>-2172116928000</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 10 */
 				"DateLongSwap/dateArray",
 				dateArray,
 				DateLongSwap.class,
@@ -315,16 +315,16 @@ public class DateSwapComboTest extends ComboTest {
 				/* Uon */		"@(-2172116928000)",
 				/* UonT */		"@(-2172116928000)",
 				/* UonR */		"@(\n\t-2172116928000\n)",
-				/* UrlEnc */	"_value=@(-2172116928000)",
-				/* UrlEncT */	"_value=@(-2172116928000)",
-				/* UrlEncR */	"_value=@(\n\t-2172116928000\n)",
+				/* UrlEnc */	"0=-2172116928000",
+				/* UrlEncT */	"0=-2172116928000",
+				/* UrlEncR */	"0=-2172116928000",
 				/* MsgPack */	"91D3FFFFFE0643BDFA00",
 				/* MsgPackT */	"91D3FFFFFE0643BDFA00",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>-2172116928000</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>-2172116928000</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>-2172116928000</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 11 */
 				"DateLongSwap/dateMap",
 				dateMap,
 				DateLongSwap.class,
@@ -350,7 +350,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo>-2172116928000</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo>-2172116928000</jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 12 */
 				"DateMapSwap/singleDate",
 				singleDate,
 				DateMapSwap.class,
@@ -376,7 +376,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:time>-2172116928000</jp:time>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:time>-2172116928000</jp:time>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 13 */
 				"DateMapSwap/dateArray",
 				dateArray,
 				DateMapSwap.class,
@@ -393,16 +393,16 @@ public class DateSwapComboTest extends ComboTest {
 				/* Uon */		"@((time=-2172116928000))",
 				/* UonT */		"@((time=-2172116928000))",
 				/* UonR */		"@(\n\t(\n\t\ttime=-2172116928000\n\t)\n)",
-				/* UrlEnc */	"_value=@((time=-2172116928000))",
-				/* UrlEncT */	"_value=@((time=-2172116928000))",
-				/* UrlEncR */	"_value=@(\n\t(\n\t\ttime=-2172116928000\n\t)\n)",
+				/* UrlEnc */	"0=(time=-2172116928000)",
+				/* UrlEncT */	"0=(time=-2172116928000)",
+				/* UrlEncR */	"0=(\n\ttime=-2172116928000\n)",
 				/* MsgPack */	"9181A474696D65D3FFFFFE0643BDFA00",
 				/* MsgPackT */	"9181A474696D65D3FFFFFE0643BDFA00",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li rdf:parseType='Resource'>\n<jp:time>-2172116928000</jp:time>\n</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li rdf:parseType='Resource'>\n<jp:time>-2172116928000</jp:time>\n</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li rdf:parseType='Resource'>\n      <jp:time>-2172116928000</jp:time>\n    </rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 14 */
 				"DateMapSwap/dateMap",
 				dateMap,
 				DateMapSwap.class,
@@ -428,7 +428,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<jp:foo rdf:parseType='Resource'>\n<jp:time>-2172116928000</jp:time>\n</jp:foo>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <jp:foo rdf:parseType='Resource'>\n      <jp:time>-2172116928000</jp:time>\n    </jp:foo>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 15 */
 				"DateSwap.DateMedium/singleDate",
 				singleDate,
 				DateSwap.DateMedium.class,
@@ -454,7 +454,7 @@ public class DateSwapComboTest extends ComboTest {
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Description>\n<j:value>Mar 3, 1901</j:value>\n</rdf:Description>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Description>\n    <j:value>Mar 3, 1901</j:value>\n  </rdf:Description>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 16 */
 				"DateSwap.DateMedium/dateArray",
 				dateArray,
 				DateSwap.DateMedium.class,
@@ -471,16 +471,16 @@ public class DateSwapComboTest extends ComboTest {
 				/* Uon */		"@('Mar 3, 1901')",
 				/* UonT */		"@('Mar 3, 1901')",
 				/* UonR */		"@(\n\t'Mar 3, 1901'\n)",
-				/* UrlEnc */	"_value=@('Mar+3,+1901')",
-				/* UrlEncT */	"_value=@('Mar+3,+1901')",
-				/* UrlEncR */	"_value=@(\n\t'Mar+3,+1901'\n)",
+				/* UrlEnc */	"0='Mar+3,+1901'",
+				/* UrlEncT */	"0='Mar+3,+1901'",
+				/* UrlEncR */	"0='Mar+3,+1901'",
 				/* MsgPack */	"91AB4D617220332C2031393031",
 				/* MsgPackT */	"91AB4D617220332C2031393031",
 				/* RdfXml */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Mar 3, 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlT */	"<rdf:RDF>\n<rdf:Seq>\n<rdf:li>Mar 3, 1901</rdf:li>\n</rdf:Seq>\n</rdf:RDF>\n",
 				/* RdfXmlR */	"<rdf:RDF>\n  <rdf:Seq>\n    <rdf:li>Mar 3, 1901</rdf:li>\n  </rdf:Seq>\n</rdf:RDF>\n",
 			},
-			{
+			{	/* 17 */
 				"DateSwap.DateMedium/dateMap",
 				dateMap,
 				DateSwap.DateMedium.class,

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializerTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializerTest.java b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializerTest.java
index 91dc1a6..66e50dc 100755
--- a/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializerTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializerTest.java
@@ -81,8 +81,8 @@ public class UrlEncodingSerializerTest {
 		// Empty array
 		// Top level
 		t = new String[0];
-		assertEquals("_value=@()", s.serialize(t));
-		assertEquals("_value=@()", sr.serialize(t));
+		assertEquals("", s.serialize(t));
+		assertEquals("", sr.serialize(t));
 
 		// 2nd level in map
 		t = new ObjectMap("{x:[]}");
@@ -91,14 +91,14 @@ public class UrlEncodingSerializerTest {
 
 		// Empty 2 dimensional array
 		t = new String[1][0];
-		assertEquals("_value=@(@())", s.serialize(t));
-		assertEquals("_value=@(\n\t@()\n)", sr.serialize(t));
+		assertEquals("0=@()", s.serialize(t));
+		assertEquals("0=@()", sr.serialize(t));
 
 		// Array containing empty string
 		// Top level
 		t = new String[]{""};
-		assertEquals("_value=@('')", s.serialize(t));
-		assertEquals("_value=@(\n\t''\n)", sr.serialize(t));
+		assertEquals("0=''", s.serialize(t));
+		assertEquals("0=''", sr.serialize(t));
 
 		// 2nd level
 		t = new ObjectMap("{x:['']}");
@@ -107,8 +107,8 @@ public class UrlEncodingSerializerTest {
 
 		// Array containing 3 empty strings
 		t = new String[]{"","",""};
-		assertEquals("_value=@('','','')", s.serialize(t));
-		assertEquals("_value=@(\n\t'',\n\t'',\n\t''\n)", sr.serialize(t));
+		assertEquals("0=''&1=''&2=''", s.serialize(t));
+		assertEquals("0=''\n&1=''\n&2=''", sr.serialize(t));
 
 		// String containing \u0000
 		// Top level

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/BeanSession.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanSession.java b/juneau-core/src/main/java/org/apache/juneau/BeanSession.java
index b033324..1fb0464 100644
--- a/juneau-core/src/main/java/org/apache/juneau/BeanSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/BeanSession.java
@@ -732,7 +732,7 @@ public class BeanSession extends Session {
 		if (m == null)
 			return null;
 		T bean = null;
-		if (m.constructorArgs.length == 0) 
+		if (m.constructorArgs.length == 0)
 			bean = newBean(outer, c);
 		return new BeanMap<T>(this, bean, m);
 	}
@@ -774,7 +774,7 @@ public class BeanSession extends Session {
 			return null;
 		try {
 			T o = (T)m.newBean(outer);
-			if (o == null) 
+			if (o == null)
 				throw new BeanRuntimeException(c, "Class does not have a no-arg constructor.");
 			return o;
 		} catch (BeanRuntimeException e) {
@@ -843,13 +843,13 @@ public class BeanSession extends Session {
 	}
 
 	/**
-	 * Given an array of {@link Class} objects, returns an array of corresponding {@link ClassMeta} objects.
+	 * Given an array of {@link Type} objects, returns an array of corresponding {@link ClassMeta} objects.
 	 * Constructs a new array on each call.
 	 *
 	 * @param classes The array of classes to get class metas for.
 	 * @return An array of {@link ClassMeta} objects corresponding to the classes.  Never <jk>null</jk>.
 	 */
-	public final ClassMeta<?>[] getClassMetas(Class<?>[] classes) {
+	public final ClassMeta<?>[] getClassMetas(Type[] classes) {
 		assertFieldNotNull(classes, "classes");
 		ClassMeta<?>[] cm = new ClassMeta<?>[classes.length];
 		for (int i = 0; i < classes.length; i++)

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java
index e320777..fa3fed1 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java
@@ -54,7 +54,7 @@ public final class ClassMeta<T> implements Type {
 
 	/** Class categories. */
 	enum ClassCategory {
-		MAP, COLLECTION, CLASS, NUMBER, DECIMAL, BOOLEAN, CHAR, DATE, ARRAY, ENUM, OTHER, CHARSEQ, STR, OBJ, URI, BEANMAP, READER, INPUTSTREAM
+		MAP, COLLECTION, CLASS, NUMBER, DECIMAL, BOOLEAN, CHAR, DATE, ARRAY, ENUM, OTHER, CHARSEQ, STR, OBJ, URI, BEANMAP, READER, INPUTSTREAM, VOID
 	}
 
 	final Class<T> innerClass;                              // The class being wrapped.
@@ -83,6 +83,7 @@ public final class ClassMeta<T> implements Type {
 	private final Object primitiveDefault;                  // Default value for primitive type classes.
 	private final Map<String,Method>
 		remoteableMethods,                                   // Methods annotated with @Remoteable.  Contains all public methods if class is annotated with @Remotable.
+		proxyableMethods,                                    // Remoteable methods only if at least one method is marked @Remoteable, otherwise all public methods.
 		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<?,?>>
@@ -159,6 +160,7 @@ public final class ClassMeta<T> implements Type {
 		this.primitiveDefault = builder.primitiveDefault;
 		this.publicMethods = builder.publicMethods;
 		this.remoteableMethods = builder.remoteableMethods;
+		this.proxyableMethods = builder.proxyableMethods;
 		this.beanFilter = beanFilter;
 		this.pojoSwap = builder.pojoSwap;
 		this.extMeta = new MetadataMap();
@@ -208,6 +210,7 @@ public final class ClassMeta<T> implements Type {
 		this.isMemberClass = mainType.isMemberClass;
 		this.primitiveDefault = mainType.primitiveDefault;
 		this.remoteableMethods = mainType.remoteableMethods;
+		this.proxyableMethods = mainType.proxyableMethods;
 		this.publicMethods = mainType.publicMethods;
 		this.beanContext = mainType.beanContext;
 		this.serializedClassMeta = this;
@@ -253,7 +256,8 @@ public final class ClassMeta<T> implements Type {
 		Object primitiveDefault = null;
 		Map<String,Method>
 			publicMethods = new LinkedHashMap<String,Method>(),
-			remoteableMethods = null;
+			remoteableMethods = null,
+			proxyableMethods = null;
 		ClassMeta<?>
 			keyType = null,
 			valueType = null,
@@ -294,6 +298,8 @@ public final class ClassMeta<T> implements Type {
 				}
 				else if (c == Character.TYPE)
 					cc = CHAR;
+				else if (c == void.class || c == Void.class)
+					cc = VOID;
 			} else {
 				if (isParentClass(Delegate.class, c))
 					isDelegate = true;
@@ -479,7 +485,7 @@ public final class ClassMeta<T> implements Type {
 					publicMethods.put(ClassUtils.getMethodSignature(m), m);
 
 			if (c.getAnnotation(Remoteable.class) != null) {
-				remoteableMethods = publicMethods;
+				remoteableMethods = proxyableMethods = publicMethods;
 			} else {
 				for (Method m : c.getMethods()) {
 					if (m.getAnnotation(Remoteable.class) != null) {
@@ -488,6 +494,7 @@ public final class ClassMeta<T> implements Type {
 						remoteableMethods.put(ClassUtils.getMethodSignature(m), m);
 					}
 				}
+				proxyableMethods = (remoteableMethods != null ? remoteableMethods : publicMethods);
 			}
 
 			if (innerClass != Object.class) {
@@ -1050,6 +1057,15 @@ public final class ClassMeta<T> implements Type {
 	}
 
 	/**
+	 * Returns <jk>true</jk> if this class is {@link Void} or <jk>void</jk>.
+	 *
+	 * @return <jk>true</jk> if this class is {@link Void} or <jk>void</jk>.
+	 */
+	public boolean isVoid() {
+		return cc == VOID;
+	}
+
+	/**
 	 * Returns <jk>true</jk> if instance of this object can be <jk>null</jk>.
 	 * <p>
 	 * Objects can be <jk>null</jk>, but primitives cannot, except for chars which can be represented
@@ -1099,6 +1115,17 @@ public final class ClassMeta<T> implements Type {
 	}
 
 	/**
+	 * All methods on this class that can be exposed as a proxy method.
+	 * <p>
+	 * Same as {@link #getRemoteableMethods()} except returns all public methods if class is not annotated with {@link Remoteable @Remotable}.
+	 *
+	 * @return All proxyable methods on this class.
+	 */
+	public Map<String,Method> getProxyableMethods() {
+		return proxyableMethods;
+	}
+
+	/**
 	 * All public methods on this class including static methods.
 	 * Keys are method signatures.
 	 *

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
index ef73860..75f2760 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
@@ -111,6 +111,9 @@ public class HtmlParser extends XmlParser {
 		boolean isValid = true;
 		HtmlTag tag = (event == CHARACTERS ? null : HtmlTag.forString(r.getName().getLocalPart(), false));
 
+		if (tag == HTML)
+			tag = skipToData(r);
+
 		if (isEmpty) {
 			o = "";
 		} else if (tag == null || tag.isOneOf(BR,BS,FF,SP)) {
@@ -164,6 +167,12 @@ public class HtmlParser extends XmlParser {
 				isValid = false;
 			skipTag(r, xBOOLEAN);
 
+		} else if (tag == P) {
+			String text = session.getElementText(r);
+			if (! "No Results".equals(text))
+				isValid = false;
+			skipTag(r, xP);
+
 		} else if (tag == NULL) {
 			skipTag(r, NULL);
 			skipTag(r, xNULL);
@@ -246,6 +255,26 @@ public class HtmlParser extends XmlParser {
 		return (T)o;
 	}
 
+	/**
+	 * For parsing output from HtmlDocSerializer, this skips over the head, title, and links.
+	 */
+	private static HtmlTag skipToData(XMLStreamReader r) throws XMLStreamException {
+		while (true) {
+			int event = r.next();
+			if (event == START_ELEMENT && "div".equals(r.getLocalName()) && "data".equals(r.getAttributeValue(null, "id"))) {
+				r.nextTag();
+				event = r.getEventType();
+				boolean isEmpty = (event == END_ELEMENT);
+				// Skip until we find a start element, end document, or non-empty text.
+				if (! isEmpty)
+					event = skipWs(r);
+				if (event == END_DOCUMENT)
+					throw new XMLStreamException("Unexpected end of stream looking for data.", r.getLocation());
+				return (event == CHARACTERS ? null : HtmlTag.forString(r.getName().getLocalPart(), false));
+			}
+		}
+	}
+
 	private static String getAttribute(XMLStreamReader r, String name, String def) {
 		for (int i = 0; i < r.getAttributeCount(); i++)
 			if (r.getAttributeLocalName(i).equals(name))
@@ -327,10 +356,19 @@ public class HtmlParser extends XmlParser {
 	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
 	 */
 	private Object[] parseArgs(HtmlParserSession session, XMLStreamReader r, ClassMeta<?>[] argTypes) throws Exception {
+		HtmlTag tag = HtmlTag.forEvent(r);
+
+		// Special case:
+		// Serializing args containing a single bean (or multiple beans of the same type) will end up serialized as a <table _type='array'>
+		if (tag == TABLE) {
+			List<Object> l = (List<Object>)parseAnything(session, session.getClassMeta(List.class, argTypes[0]), r, session.getOuter(), true, null);
+			return l.toArray(new Object[l.size()]);
+		}
+
 		Object[] o = new Object[argTypes.length];
 		int i = 0;
 		while (true) {
-			HtmlTag tag = nextTag(r, LI, xUL);
+			tag = nextTag(r, LI, xUL);
 			if (tag == xUL)
 				break;
 			o[i] = parseAnything(session, argTypes[i], r, session.getOuter(), false, null);

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
index efa36e2..74a98ce 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java
@@ -40,6 +40,8 @@ enum HtmlTag {
 	FF(13,"<ff>"),		// formfeed
 	BS(14,"<bs>"),		// backspace
 	SP(17, "<sp>"),   // space
+	P(18, "<p>"),
+	HTML(19, "<html>"),
 	xTABLE(-1,"</table>"),
 	xTR(-2,"</tr>"),
 	xTH(-3,"</th>"),
@@ -54,7 +56,9 @@ enum HtmlTag {
 	xBR(-12,"</br>"),
 	xFF(-13,"</ff>"),
 	xBS(-14,"</bs>"),
-	xSP(-17, "</sp>");
+	xSP(-17, "</sp>"),
+	xP(-18, "</p>"),
+	xHTML(-19, "</html>");
 
 	private Map<Integer,HtmlTag> cache = new HashMap<Integer,HtmlTag>();
 
@@ -121,6 +125,10 @@ enum HtmlTag {
 		}
 		else if (c == 'f')
 			t = (end ? xFF : FF);
+		else if (c == 'p')
+			t = (end ? xP : P);
+		else if (c == 'h')
+			t = (end ? xHTML : HTML);
 		if (t == null)
 			throw new XMLStreamException("Unknown tag '"+tag+"' encountered");
 		return t;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/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 4ba239f..39bf16b 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
@@ -624,4 +624,45 @@ public final class ClassUtils {
 //	private Class<?> findClass(String name) throws ClassNotFoundException {
 //		return classLoader == null ? Class.forName(name) : Class.forName(name, true, classLoader);
 //	}
+
+	/**
+	 * Returns a {@link MethodInfo} bean that describes the specified method.
+	 * @param m The method to describe.
+	 * @return The bean with information about the method.
+	 */
+	public static MethodInfo getMethodInfo(Method m) {
+		return new MethodInfo(m);
+	}
+
+	/**
+	 * Returns {@link MethodInfo} beans that describe the specified methods.
+	 * @param m The methods to describe.
+	 * @return The beans with information about the methods.
+	 */
+	public static MethodInfo[] getMethodInfo(Collection<Method> m) {
+		MethodInfo[] mi = new MethodInfo[m.size()];
+		int i = 0;
+		for (Method mm : m)
+			mi[i++] = getMethodInfo(mm);
+		return mi;
+	}
+
+	/**
+	 * Simple bean that shows the name, parameter types, and return type of a method.
+	 */
+	@SuppressWarnings("javadoc")
+	public static class MethodInfo {
+		public final String methodName;
+		public final String[] parameterTypes;
+		public final String returnType;
+
+		MethodInfo(Method m) {
+			methodName = m.getName();
+			Type[] pt = m.getGenericParameterTypes();
+			parameterTypes = new String[pt.length];
+			for (int i  = 0; i < pt.length; i++)
+				parameterTypes[i] = BeanContext.DEFAULT.getClassMeta(pt[i]).toString();
+			returnType = BeanContext.DEFAULT.getClassMeta(m.getGenericReturnType()).toString();
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
index ce4db63..77e0b9c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
@@ -196,7 +196,24 @@ public class MsgPackParser extends InputStreamParser {
 		return (T)o;
 	}
 
+	private Object[] parseArgs(MsgPackParserSession session, MsgPackInputStream is, ClassMeta<?>[] argTypes) throws Exception {
 
+		Object[] o = new Object[argTypes.length];
+		DataType dt = is.readDataType();
+		int length = (int)is.readLength();
+
+		if (dt != ARRAY)
+			throw new ParseException("Expected ARRAY but was {0}", dt);
+		if (length != argTypes.length)
+			throw new ParseException("Expected array length {0} but was {1}", argTypes.length, length);
+
+		for (int i = 0; i < length; i++)
+			o[i] = parseAnything(session, argTypes[i], is, null, null);
+
+		return o;
+	}
+
+	
 	//--------------------------------------------------------------------------------
 	// Entry point methods
 	//--------------------------------------------------------------------------------
@@ -213,4 +230,12 @@ public class MsgPackParser extends InputStreamParser {
 		T o = parseAnything(s, type, is, s.getOuter(), null);
 		return o;
 	}
+
+	@Override /* ReaderParser */
+	protected Object[] doParseArgs(ParserSession session, ClassMeta<?>[] argTypes) throws Exception {
+		MsgPackParserSession s = (MsgPackParserSession)session;
+		MsgPackInputStream is = s.getInputStream();
+		Object[] a = parseArgs(s, is, argTypes);
+		return a;
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
index 53ec4c6..bb79146 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java
@@ -272,6 +272,7 @@ public abstract class Parser extends CoreObject {
 	 * 	<br>Ignored if the main type is not a map or collection.
 	 * @return The parsed object.
 	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
+	 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections.
 	 */
 	@SuppressWarnings("unchecked")
 	public final <T> T parse(Object input, Type type, Type...args) throws ParseException {
@@ -288,11 +289,14 @@ public abstract class Parser extends CoreObject {
 	 * 	ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>;
 	 *
 	 * 	<jc>// Parse into a string.</jc>
-	 * 	MyBean b = p.parse(json, String.<jk>class</jk>);
+	 * 	String s = p.parse(json, String.<jk>class</jk>);
 	 *
 	 * 	<jc>// Parse into a bean.</jc>
 	 * 	MyBean b = p.parse(json, MyBean.<jk>class</jk>);
 	 *
+	 * 	<jc>// Parse into a bean array.</jc>
+	 * 	MyBean[] ba = p.parse(json, MyBean[].<jk>class</jk>);
+	 *
 	 * 	<jc>// Parse into a linked-list of objects.</jc>
 	 * 	List l = p.parse(json, LinkedList.<jk>class</jk>);
 	 *
@@ -477,7 +481,6 @@ public abstract class Parser extends CoreObject {
 	 * @param argTypes Specifies the type of objects to create for each entry in the array.
 	 * @return An array of parsed objects.
 	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
-	 * @throws UnsupportedOperationException If not implemented.
 	 */
 	public final Object[] parseArgs(Object input, ClassMeta<?>[] argTypes) throws ParseException {
 		if (argTypes == null || argTypes.length == 0)
@@ -495,6 +498,29 @@ public abstract class Parser extends CoreObject {
 	}
 
 	/**
+	 * Same as {@link #parseArgs(Object, ClassMeta[])} except allows you to pass in {@link Class} objects.
+	 *
+	 * @param input The input.  Subclasses can support different input types.
+	 * @param argTypes Specifies the type of objects to create for each entry in the array.
+	 * @return An array of parsed objects.
+	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
+	 */
+	public final Object[] parseArgs(Object input, Type[] argTypes) throws ParseException {
+		if (argTypes == null || argTypes.length == 0)
+			return new Object[0];
+		ParserSession session = createSession(input);
+		try {
+			return doParseArgs(session, session.getClassMetas(argTypes));
+		} catch (ParseException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new ParseException(session, e);
+		} finally {
+			session.close();
+		}
+	}
+
+	/**
 	 * Implementation method.
 	 * Default implementation throws an {@link UnsupportedOperationException}.
 	 * @param session The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object, Locale, TimeZone, MediaType)}.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroupBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroupBuilder.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroupBuilder.java
index 64f9253..ac44c84 100644
--- a/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroupBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroupBuilder.java
@@ -36,6 +36,18 @@ public class ParserGroupBuilder {
 	}
 
 	/**
+	 * Create an empty parser group using the specified property store for settings.
+	 * <p>
+	 * Note:  Modifying the specified property store externally will also modify it here.
+	 *
+	 * @param propertyStore The property store containing all settings common to all parsers in this group.
+	 */
+	public ParserGroupBuilder(PropertyStore propertyStore) {
+		this.parsers = new ArrayList<Object>();
+		this.propertyStore = propertyStore;
+	}
+
+	/**
 	 * Clone an existing parser group builder.
 	 * @param copyFrom The parser group that we're copying settings and parsers from.
 	 */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
index b639358..5521a9d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerGroupBuilder.java
@@ -36,6 +36,18 @@ public class SerializerGroupBuilder {
 	}
 
 	/**
+	 * Create an empty serializer group using the specified property store for settings.
+	 * <p>
+	 * Note:  Modifying the specified property store externally will also modify it here.
+	 *
+	 * @param propertyStore The property store containing all settings common to all serializers in this group.
+	 */
+	public SerializerGroupBuilder(PropertyStore propertyStore) {
+		this.serializers = new ArrayList<Object>();
+		this.propertyStore = propertyStore;
+	}
+
+	/**
 	 * Clone an existing serializer group builder.
 	 * @param copyFrom The serializer group that we're copying settings and serializers from.
 	 */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
index b4ed5ea..817811d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java
@@ -125,6 +125,10 @@ public class UonParser extends ReaderParser {
 			else if (sType.isPrimitive())
 				o = sType.getPrimitiveDefault();
 			// Otherwise, leave null.
+		} else if (sType.isVoid()) {
+			String s = parseString(session, r, isUrlParamValue);
+			if (s != null)
+				throw new ParseException(session, "Expected ''null'' for void value, but was ''{0}''.", s);
 		} else if (sType.isObject()) {
 			if (c == '(') {
 				ObjectMap m = new ObjectMap(session);
@@ -138,10 +142,12 @@ public class UonParser extends ReaderParser {
 				if (c != '\'') {
 					if ("true".equals(s) || "false".equals(s))
 						o = Boolean.valueOf(s);
-					else if (StringUtils.isNumeric(s))
-						o = StringUtils.parseNumber(s, Number.class);
-					else
-						o = s;
+					else if (! "null".equals(s)) {
+						if (StringUtils.isNumeric(s))
+							o = StringUtils.parseNumber(s, Number.class);
+						else
+							o = s;
+					}
 				} else {
 					o = s;
 				}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
index f6de298..89d68ea 100644
--- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParser.java
@@ -112,9 +112,9 @@ public class UrlEncodingParser extends UonParser {
 				o = session.cast(m, null, eType);
 			else if (m.containsKey("_value"))
 				o = session.convertToType(m.get("_value"), sType);
-			else if (sType.isCollection()) {
+			else if (sType.isCollection() || sType.isArray()) {
 				// ?1=foo&2=bar...
-				Collection c2 = sType.canCreateNewInstance() ? (Collection)sType.newInstance() : new ObjectList(session);
+				Collection c2 = (sType.isArray() || ! sType.canCreateNewInstance(outer)) ? new ObjectList(session) : (Collection)sType.newInstance();
 				Map<Integer,Object> t = new TreeMap<Integer,Object>();
 				for (Map.Entry<String,Object> e : m.entrySet()) {
 					String k = e.getKey();
@@ -122,7 +122,7 @@ public class UrlEncodingParser extends UonParser {
 						t.put(Integer.valueOf(k), session.convertToType(e.getValue(), sType.getElementType()));
 				}
 				c2.addAll(t.values());
-				o = c2;
+				o = (sType.isArray() ? ArrayUtils.toArray(c2, sType.getElementType().getInnerClass()) : c2);
 			} else {
 				if (sType.getNotABeanReason() != null)
 					throw new ParseException(session, "Class ''{0}'' could not be instantiated as application/x-www-form-urlencoded.  Reason: ''{1}''", sType, sType.getNotABeanReason());
@@ -413,18 +413,76 @@ public class UrlEncodingParser extends UonParser {
 	}
 
 	private Object[] parseArgs(UrlEncodingParserSession session, ParserReader r, ClassMeta<?>[] argTypes) throws Exception {
-		// TODO - This can be made more efficient.
-		ClassMeta<TreeMap<Integer,String>> cm = session.getClassMeta(TreeMap.class, Integer.class, String.class);
-		TreeMap<Integer,String> m = parseAnything(session, cm, r, session.getOuter());
-		Object[] vals = m.values().toArray(new Object[m.size()]);
-		if (vals.length != argTypes.length)
-			throw new ParseException(session, "Argument lengths don't match.  vals={0}, argTypes={1}", vals.length, argTypes.length);
-		for (int i = 0; i < vals.length; i++) {
-			String s = String.valueOf(vals[i]);
-			vals[i] = super.parseAnything(session, argTypes[i], new UonReader(s, false), session.getOuter(), true, null);
+
+		int c = r.peekSkipWs();
+		if (c == '?')
+			r.read();
+
+		Object[] vals = new Object[argTypes.length];
+
+		final int S1=1; // Looking for attrName start.
+		final int S2=2; // Found attrName end, looking for =.
+		final int S3=3; // Found =, looking for valStart.
+		final int S4=4; // Looking for & or end.
+		boolean isInEscape = false;
+
+		int state = S1;
+		Integer currIdx = 0;
+		while (c != -1) {
+			c = r.read();
+			if (! isInEscape) {
+				if (state == S1) {
+					if (c == -1)
+						return vals;
+					r.unread();
+					Object attr = parseAttr(session, r, true);
+					currIdx = Integer.parseInt(attr.toString());
+					if (currIdx >= vals.length)
+						throw new ParseException(session, "Out-of-range index encountered.  args length={0}, index={1}", vals.length, currIdx);
+					state = S2;
+					c = 0; // Avoid isInEscape if c was '\'
+				} else if (state == S2) {
+					if (c == '\u0002')
+						state = S3;
+					else if (c == -1 || c == '\u0001') {
+						vals[currIdx] = null;
+						if (c == -1)
+							return vals;
+						state = S1;
+					}
+				} else if (state == S3) {
+					if (c == -1 || c == '\u0001') {
+						vals[currIdx] = convertAttrToType(session, null, "", argTypes[currIdx]);
+						if (c == -1)
+							return vals;
+						state = S1;
+					} else  {
+						// For performance, we bypass parseAnything for string values.
+						vals[currIdx] = argTypes[currIdx].isString() ? super.parseString(session, r.unread(), true) : super.parseAnything(session, argTypes[currIdx], r.unread(), null, true, null);
+
+						state = S4;
+						c = 0; // Avoid isInEscape if c was '\'
+					}
+				} else if (state == S4) {
+					if (c == '\u0001')
+						state = S1;
+					else if (c == -1) {
+						return vals;
+					}
+				}
+			}
+			isInEscape = (c == '\\' && ! isInEscape);
 		}
+		if (state == S1)
+			throw new ParseException(session, "Could not find attribute name on object.");
+		if (state == S2)
+			throw new ParseException(session, "Could not find '=' following attribute name on object.");
+		if (state == S3)
+			throw new ParseException(session, "Dangling '=' found in object entry");
+		if (state == S4)
+			throw new ParseException(session, "Could not find end of object.");
 
-		return vals;
+		return null; // Unreachable.
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
index 665ff27..222ad7c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
@@ -239,8 +239,9 @@ public class UrlEncodingSerializer extends UonSerializer {
 				serializeMap(session, out, (Map)o, sType);
 		} else if (sType.isBean()) {
 			serializeBeanMap(session, out, session.toBeanMap(o), typeName);
-		} else if (sType.isCollection()) {
-			serializeMap(session, out, getCollectionMap((Collection)o), session.getClassMeta(Map.class, Integer.class, sType.getElementType()));
+		} else if (sType.isCollection() || sType.isArray()) {
+			Map m = sType.isCollection() ? getCollectionMap((Collection)o) : getCollectionMap(o);
+			serializeCollectionMap(session, out, m, session.getClassMeta(Map.class, Integer.class, Object.class));
 		} else {
 			// All other types can't be serialized as key/value pairs, so we create a
 			// mock key/value pair with a "_value" key.
@@ -252,6 +253,9 @@ public class UrlEncodingSerializer extends UonSerializer {
 		return out;
 	}
 
+	/**
+	 * Converts a Collection into an integer-indexed map.
+	 */
 	private static Map<Integer,Object> getCollectionMap(Collection<?> c) {
 		Map<Integer,Object> m = new TreeMap<Integer,Object>();
 		int i = 0;
@@ -260,6 +264,16 @@ public class UrlEncodingSerializer extends UonSerializer {
 		return m;
 	}
 
+	/**
+	 * Converts an array into an integer-indexed map.
+	 */
+	private static Map<Integer,Object> getCollectionMap(Object array) {
+		Map<Integer,Object> m = new TreeMap<Integer,Object>();
+		for (int i = 0; i < Array.getLength(array); i++)
+			m.put(i, Array.get(array, i));
+		return m;
+	}
+
 	@SuppressWarnings({ "rawtypes", "unchecked" })
 	private SerializerWriter serializeMap(UrlEncodingSerializerSession session, UonWriter out, Map m, ClassMeta<?> type) throws Exception {
 
@@ -270,13 +284,9 @@ public class UrlEncodingSerializer extends UonSerializer {
 		int depth = session.getIndent();
 		boolean addAmp = false;
 
-		Iterator mapEntries = m.entrySet().iterator();
-
-		while (mapEntries.hasNext()) {
-			Map.Entry e = (Map.Entry) mapEntries.next();
-			Object value = e.getValue();
+		for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
 			Object key = session.generalize(e.getKey(), keyType);
-
+			Object value = e.getValue();
 
 			if (session.shouldUseExpandedParams(value)) {
 				Iterator i = value instanceof Collection ? ((Collection)value).iterator() : ArrayUtils.iterator(value);
@@ -299,6 +309,25 @@ public class UrlEncodingSerializer extends UonSerializer {
 		return out;
 	}
 
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private SerializerWriter serializeCollectionMap(UrlEncodingSerializerSession session, UonWriter out, Map m, ClassMeta<?> type) throws Exception {
+
+		ClassMeta<?> valueType = type.getValueType();
+
+		int depth = session.getIndent();
+		boolean addAmp = false;
+
+		for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
+			if (addAmp)
+				out.cr(depth).append('&');
+			out.append(e.getKey()).append('=');
+			super.serializeAnything(session, out, e.getValue(), valueType, null, null);
+			addAmp = true;
+		}
+
+		return out;
+	}
+
 	@SuppressWarnings({ "rawtypes" })
 	private SerializerWriter serializeBeanMap(UrlEncodingSerializerSession session, UonWriter out, BeanMap<?> m, String typeName) throws Exception {
 		int depth = session.getIndent();

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/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 9a959d0..66952a8 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -5723,6 +5723,8 @@
 				<li>New {@link org.apache.juneau.svl.vars.SwitchVar $SWITCH} variable for switch block logic.
 				<li>Whitespace wasn't being ignored in some cases.
 			</ul>
+			<li>{@link org.apache.juneau.html.HtmlParser} can now parse full body contents generated by {@link org.apache.juneau.html.HtmlDocSerializer}. 
+			<li>Parse-args supported added to {@link org.apache.juneau.msgpack.MsgPackParser} to allow it to be used in remoteable proxies. 
 		</ul>
 		
 		<h6 class='topic'>org.apache.juneau.rest</h6>
@@ -5772,11 +5774,44 @@
 			<li>{@link org.apache.juneau.rest.annotation.RestResource#stylesheet()} can now take in a comma-delimited list of stylesheet paths.
 			<li>{@link org.apache.juneau.rest.StreamResource} can now contain multiple sources from a variety of source types (e.g. <code><jk>byte</jk>[]</code> arrays, <code>InputStreams</code>, <code>Files</code>, etc...)
 				and is now immutable.  It also includes a new {@link org.apache.juneau.rest.StreamResource.Builder} class.
+			<li>Simplified remoteable proxies using the <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code> annotation on REST methods.
+				Used to expose interface proxies without the need for {@link org.apache.juneau.rest.remoteable.RemoteableServlet}.
+			<p class='bcode'>
+	<jc>// Server side</jc>
+	<ja>@RestMethod</ja>(name=<js>"PROXY"</js>, path=<js>"/myproxy/*"</js>)
+	<jk>public</jk> IAddressBook getProxy() {
+		<jk>return</jk> <jf>addressBook</jf>;
+	}
+
+	<jc>// Client side</jc>
+	RestClient client = <jk>new</jk> RestClientBuilder().rootUrl(<jf>samplesUrl</jf>).build();			
+	IAddressBook ab = client.getRemoteableProxy(IAddressBook.<jk>class</jk>, <js>"/addressBook/myproxy"</js>);
+			</p>
+				See {@link org.apache.juneau.rest.annotation.RestMethod#name()} for more information.
+			<li>{@link org.apache.juneau.rest.RestRequest#toString()} can be called at any time to view the headers and content of the request
+				without affecting functionality.  Very useful for debugging.
 		</ul>
 
 		<h6 class='topic'>org.apache.juneau.rest.client</h6>
 		<ul class='spaced-list'>
 			<li>Revamped the client API to use builders.
+			<li>New methods added to {@link org.apache.juneau.rest.client.RestCall}:
+			<ul>
+				<li>{@link org.apache.juneau.rest.client.RestCall#serializer(Serializer)} - Override the serializer defined on the client for a single call.
+				<li>{@link org.apache.juneau.rest.client.RestCall#parser(Parser)} - Override the parser defined on the client for a single call.
+			</ul>
+			<li>New methods added to {@link org.apache.juneau.rest.client.RestClient}:
+			<ul>
+				<li>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object)} - For interface proxies defined using <code><ja>@RestMethod</ja>(name=<js>"PROXY"</js>)</code>.
+				<li>{@link org.apache.juneau.rest.client.RestClient#getRemoteableProxy(Class,Object,Serializer,Parser)} - Same as above, but overrides the serializer and parser defined on the client.
+			</ul>
+			<li>New methods added to {@link org.apache.juneau.rest.client.RestClientBuilder}:
+			<ul>
+				<li>{@link org.apache.juneau.rest.client.RestClientBuilder#noTrace()} - Adds a <code>No-Trace: true</code> header on all requests to prevent
+					the servlet from logging errors.
+					Useful for testing scenarios when you don't want the console to end up showing errors done on purpose.
+				<li>{@link org.apache.juneau.rest.client.RestClientBuilder#debug(boolean)} now adds a <code>Debug: true</code> header on all requests.
+			</ul>
 		</ul>
 		
 		<h6 class='topic'>org.apache.juneau.microservice</h6>

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/AddressBook.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/AddressBook.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/AddressBook.java
index 28935df..ede24f2 100755
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/AddressBook.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/AddressBook.java
@@ -35,6 +35,27 @@ public class AddressBook extends LinkedList<Person> implements IAddressBook {
 	public AddressBook(URI uri) throws Exception {
 		this.uri = uri;
 	}
+	
+	@Override /* IAddressBook */
+	public void init() throws Exception {
+		clear();
+		createPerson(
+			new CreatePerson(
+				"Barack Obama",
+				toCalendar("Aug 4, 1961"),
+				new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, true),
+				new CreateAddress("5046 S Greenwood Ave", "Chicago", "IL", 60615, false)
+			)
+		);
+		createPerson(
+			new CreatePerson(
+				"George Walker Bush",
+				toCalendar("Jul 6, 1946"),
+				new CreateAddress("43 Prairie Chapel Rd", "Crawford", "TX", 76638, true),
+				new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, false)
+			)
+		);
+	}
 
 	@Override /* IAddressBook */
 	public List<Person> getPeople() {
@@ -97,6 +118,4 @@ public class AddressBook extends LinkedList<Person> implements IAddressBook {
 		c.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.US).parse(birthDate));
 		return c;
 	}
-}
-
-
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
index 570fe06..1c7431a 100755
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/addressbook/IAddressBook.java
@@ -19,6 +19,9 @@ import java.util.*;
  * See {@link SampleRemoteableServlet}.
  */
 public interface IAddressBook {
+	
+	/** Initialize this address book with preset entries */
+	void init() throws Exception;
 
 	/** Return all people in the address book */
 	List<Person> getPeople();

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
index b8a7a50..4a49a60 100644
--- a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
+++ b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/addressbook/AddressBookResource.java
@@ -13,7 +13,6 @@
 package org.apache.juneau.examples.rest.addressbook;
 
 import static javax.servlet.http.HttpServletResponse.*;
-import static org.apache.juneau.examples.addressbook.AddressBook.*;
 import static org.apache.juneau.html.HtmlDocSerializerContext.*;
 import static org.apache.juneau.jena.RdfCommonContext.*;
 import static org.apache.juneau.jena.RdfSerializerContext.*;
@@ -74,23 +73,8 @@ public class AddressBookResource extends ResourceJena {
 			// Create the address book
 			addressBook = new AddressBook(java.net.URI.create(""));
 
-			// Add some people to our address book by default
-			addressBook.createPerson(
-				new CreatePerson(
-					"Barack Obama",
-					toCalendar("Aug 4, 1961"),
-					new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, true),
-					new CreateAddress("5046 S Greenwood Ave", "Chicago", "IL", 60615, false)
-				)
-			);
-			addressBook.createPerson(
-				new CreatePerson(
-					"George Walker Bush",
-					toCalendar("Jul 6, 1946"),
-					new CreateAddress("43 Prairie Chapel Rd", "Crawford", "TX", 76638, true),
-					new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, false)
-				)
-			);
+			// Initialize it with some contents.
+			addressBook.init();
 
 		} catch (Exception e) {
 			throw new RuntimeException(e);
@@ -292,6 +276,15 @@ public class AddressBookResource extends ResourceJena {
 	}
 
 	/**
+	 * [PROXY /*]
+	 * Return a proxy interface to IAddressBook.
+	 */
+	@RestMethod(name="PROXY", path="/proxy/*")
+	public IAddressBook getProxy() {
+		return addressBook;
+	}
+
+	/**
 	 * [OPTIONS /*]
 	 * View resource options
 	 */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9db2e03f/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
----------------------------------------------------------------------
diff --git a/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java b/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
index 5f3f602..8dfa7fd 100644
--- a/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
+++ b/juneau-examples-rest/src/test/java/org/apache/juneau/examples/rest/AddressBookResourceTest.java
@@ -258,4 +258,44 @@ public class AddressBookResourceTest extends RestTestcase {
 			assertObjectEquals("['B','r','ck Obama']", tokens);
 		}
 	}
+
+	//====================================================================================================
+	// Interface proxy tests
+	//====================================================================================================
+	@Test
+	public void testProxyInterface() throws Exception {
+
+		for (RestClient client : clients) {
+
+			List<Person> people;
+
+			IAddressBook ab = client.getRemoteableProxy(IAddressBook.class, "/addressBook/proxy");
+				
+			// Reinitialize the resource
+			ab.init();
+
+			// Simple GETs
+			people = ab.getPeople();
+			assertEquals("Barack Obama", people.get(0).name);
+			assertEquals(76638, people.get(1).addresses.get(0).zip);
+
+			// POST a person
+			CreatePerson billClinton = new CreatePerson("Bill Clinton", AddressBook.toCalendar("Aug 19, 1946"),
+				new CreateAddress("a3","b3","c3",3,false)
+			);
+			ab.createPerson(billClinton);
+			people = ab.getPeople();
+			assertEquals(3, people.size());
+			Person p = people.get(2);
+			assertEquals("Bill Clinton", p.name);
+
+			// DELETE a person
+			ab.removePerson(p.id);
+			people = ab.getPeople();
+			assertEquals(2, people.size());
+
+			// Reinitialize the resource
+			ab.init();
+		}
+	}
 }