Add MockRestClient.pathVars().
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java
index 1c33968..8cea4fd 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java
@@ -16,6 +16,7 @@
 
 import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.serializer.*;
 
@@ -92,6 +93,28 @@
 		return new AMap<K,V>().a(key, value);
 	}
 
+	/**
+	 * Creates a map out of a list of key/value pairs.
+	 *
+	 * @param <K> The key type.
+	 * @param <V> The value type.
+	 * @param parameters
+	 * 	The parameters.
+	 * 	<br>Must be an even number of parameters.
+	 * 	<br>It's up to you to ensure that the parameters are the correct type.
+	 * @return A new map.
+	 */
+	@SuppressWarnings("unchecked")
+	public static <K,V> AMap<K,V> ofPairs(Object...parameters) {
+		AMap<K,V> m = AMap.of();
+		if (parameters.length % 2 != 0)
+			throw new BasicRuntimeException("Odd number of parameters passed into AMap.ofPairs()");
+		for (int i = 0; i < parameters.length; i+=2)
+			m.put((K)parameters[i], (V)parameters[i+1]);
+		return m;
+	}
+
+
 	@SuppressWarnings("javadoc")
 	public static <K,V> AMap<K,V> of(K k1, V v1, K k2, V v2) {
 		return AMap.of(k1,v1).a(k2,v2);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java
index 2ad0b4c..ac8f7b2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java
@@ -224,6 +224,27 @@
 	}
 
 	/**
+	 * Returns <jk>true</jk> if the specified object is not empty.
+	 *
+	 * <p>
+	 * Return <jk>false</jk> if the value is any of the following:
+	 * <ul>
+	 * 	<li><jk>null</jk>
+	 * 	<li>An empty Collection
+	 * 	<li>An empty Map
+	 * 	<li>An empty array
+	 * 	<li>An empty CharSequence
+	 * 	<li>An empty String when serialized to a string using {@link Object#toString()}.
+	 * </ul>
+	 *
+	 * @param o The object to test.
+	 * @return <jk>true</jk> if the specified object is empty.
+	 */
+	public static boolean isNotEmpty(Object o) {
+		return ! isEmpty(o);
+	}
+
+	/**
 	 * Returns the first non-null value in the specified array
 	 *
 	 * @param t The values to check.
@@ -237,5 +258,4 @@
 					return tt;
 		return null;
 	}
-
 }
diff --git a/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/mock2/MockRestClient_PathVars.java b/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/mock2/MockRestClient_PathVars.java
new file mode 100644
index 0000000..bed1818
--- /dev/null
+++ b/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/mock2/MockRestClient_PathVars.java
@@ -0,0 +1,43 @@
+// ***************************************************************************************************************************
+// * 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.mock2;
+
+import static org.junit.runners.MethodSorters.*;
+
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.rest.annotation.*;
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class MockRestClient_PathVars {
+
+	@Rest
+	public static class A {
+		@RestMethod
+		public String get(@Path("foo") String foo) {
+			return foo;
+		}
+	}
+
+	@Test
+	public void a01_basic() throws Exception {
+		MockRestClient
+			.create(A.class)
+			.pathVars("foo","bar")
+			.build()
+			.get("/")
+			.run()
+			.assertStatus().code().is(200)
+			.assertBody().is("bar");
+	}
+}
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
index 634c1ea..09d9492 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
@@ -223,7 +223,8 @@
 		MOCKRESTCLIENT_restBeanCtx = PREFIX + "restBeanCtx.o",
 		MOCKRESTCLIENT_servletPath = PREFIX + "servletPath.s",
 		MOCKRESTCLIENT_contextPath = PREFIX + "contextPath.s",
-		MOCKRESTCLIENT_mockHttpClientConnectionManager = PREFIX + "mockHttpClientConnectionManager.o";
+		MOCKRESTCLIENT_mockHttpClientConnectionManager = PREFIX + "mockHttpClientConnectionManager.o",
+		MOCKRESTCLIENT_pathVars = PREFIX + "pathVars.oms";
 
 
 	private static Map<Class<?>,RestContext>
@@ -236,6 +237,7 @@
 
 	private final RestContext restBeanCtx;
 	private final String contextPath, servletPath;
+	private final Map<String,String> pathVars;
 
 	private final ThreadLocal<HttpRequest> rreq = new ThreadLocal<>();
 	private final ThreadLocal<MockRestResponse> rres = new ThreadLocal<>();
@@ -254,6 +256,7 @@
 		this.restBeanCtx = getInstanceProperty(MOCKRESTCLIENT_restBeanCtx, RestContext.class, null);
 		this.contextPath = getStringProperty(MOCKRESTCLIENT_contextPath, "");
 		this.servletPath = getStringProperty(MOCKRESTCLIENT_servletPath, "");
+		this.pathVars = getMapProperty(MOCKRESTCLIENT_pathVars, String.class);
 		getInstanceProperty(MOCKRESTCLIENT_mockHttpClientConnectionManager, MockHttpClientConnectionManager.class, null).init(this);
 	}
 
@@ -699,6 +702,7 @@
 				.create(request.getRequestLine().getMethod(), pr.getURI())
 				.contextPath(pr.getContextPath())
 				.servletPath(pr.getServletPath())
+				.pathVars(pathVars)
 				.debug(isDebug());
 
 			for (Header h : request.getAllHeaders())
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java
index f8d9666..89eedde 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClientBuilder.java
@@ -31,6 +31,7 @@
 import org.apache.http.conn.*;
 import org.apache.http.impl.client.*;
 import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.marshall.*;
@@ -144,6 +145,69 @@
 		return set(MOCKRESTCLIENT_servletPath, toValidContextPath(value));
 	}
 
+	/**
+	 * Add resolved path variables to this client.
+	 *
+	 * <p>
+	 * Allows you to add resolved parent path variables when performing tests on child resource classes.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// A parent class with a path variable.</jc>
+	 * 	<ja>@Rest</ja>(
+	 * 		path=<js>"/parent/{foo}"</js>,
+	 * 		children={
+	 * 			Child.<jk>class</jk>
+	 * 		}
+	 * 	)
+	 * 	<jk>public class</jk> Parent { ... }
+	 *
+	 * 	<jc>// A child class that uses the parent path variable.</jc>
+	 * 	<ja>@Rest</ja>
+	 * 	<jk>public class</jk> Child {
+	 *
+	 * 		<jk>@RestMethod</jk>
+	 * 		<jk>public</jk> String get(<ja>@Path</ja>(<js>"foo"</js>) String <jv>foo</jv>) {
+	 * 			<jk>return</jk> <jv>foo<jv>;
+	 * 		}
+	 * 	}
+	 * </p>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Test the method that uses the parent path variable.</jc>
+	 * 	MockRestClient
+	 * 		.<jsm>create</jsm>(Child.<jk>class</jk>)
+	 * 		.simpleJson()
+	 * 		.pathVars(<js>"foo"</js>,<js>"bar"</js>)
+	 * 		.build()
+	 * 		.get(<js>"/"</js>)
+	 * 		.run()
+	 * 		.assertStatus().code().is(200)
+	 * 		.assertBody().is(<js>"bar"</js>);
+	 * </p>
+	 *
+	 * <review>Needs review</review>
+	 *
+	 * @param value The path variables.
+	 * @return This object (for method chaining).
+	 * @see MockServletRequest#pathVars(Map)
+	 */
+	public MockRestClientBuilder pathVars(Map<String,String> value) {
+		return putAllTo(MOCKRESTCLIENT_pathVars, value);
+	}
+
+	/**
+	 * Add resolved path variables to this client.
+	 *
+	 * <p>
+	 * Identical to {@link #pathVars(Map)} but allows you to specify as a list of key/value pairs.
+	 *
+	 * @param pairs The key/value pairs.  Must be an even number of parameters.
+	 * @return This object (for method chaining).
+	 */
+	public MockRestClientBuilder pathVars(String...pairs) {
+		return pathVars(AMap.<String,String>ofPairs((Object[])pairs));
+	}
+
 	@Override /* ContextBuilder */
 	public MockRestClient build() {
 		return build(MockRestClient.class);
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java
index 04c2e0f..5c52d59 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java
@@ -143,6 +143,35 @@
 	}
 
 	/**
+	 * Adds the specified parent path variables to this servlet request.
+	 *
+	 * <p>
+	 * See {@link MockRestClientBuilder#pathVars(Map)} for an example.
+	 *
+	 * @param pathVars The
+	 * @return This object (for method chaining).
+	 * @see MockRestClientBuilder#pathVars(Map)
+	 */
+	public MockServletRequest pathVars(Map<String,String> pathVars) {
+		if (isNotEmpty(pathVars))
+			this.attributeMap.put("juneau.pathVars", new TreeMap<>(pathVars));
+		return this;
+	}
+
+	/**
+	 * Add resolved path variables to this client.
+	 *
+	 * <p>
+	 * Identical to {@link #pathVars(Map)} but allows you to specify as a list of key/value pairs.
+	 *
+	 * @param pairs The key/value pairs.  Must be an even number of parameters.
+	 * @return This object (for method chaining).
+	 */
+	public MockServletRequest pathVars(String...pairs) {
+		return pathVars(AMap.<String,String>ofPairs((Object[])pairs));
+	}
+
+	/**
 	 * Adds the specified role on this request.
 	 *
 	 * @param role The role to add to this request (e.g. <js>"ROLE_ADMIN"</js>).
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
index d2dae59..83bdfb8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
@@ -17,8 +17,6 @@
 import java.lang.reflect.*;
 import java.util.*;
 
-import javax.servlet.http.*;
-
 import org.apache.juneau.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.httppart.*;
@@ -39,22 +37,13 @@
 public class RequestPath extends TreeMap<String,String> {
 	private static final long serialVersionUID = 1L;
 
-	/**
-	 * Request attribute name for passing path variables from parent to child.
-	 */
-	static final String REST_PATHVARS_ATTR = "juneau.pathVars";
-
 	private final RestRequest req;
 	private HttpPartParserSession parser;
 
-	RequestPath(RestRequest req) {
+	RequestPath(RestCall call) {
 		super(String.CASE_INSENSITIVE_ORDER);
-		this.req = req;
-		@SuppressWarnings("unchecked")
-		Map<String,String> parentVars = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR);
-		if (parentVars != null)
-			for (Map.Entry<String,String> e : parentVars.entrySet())
-				put(e.getKey(), e.getValue());
+		this.req = call.getRestRequest();
+		putAll(call.getPathVars());
 	}
 
 	RequestPath parser(HttpPartParserSession parser) {
@@ -366,24 +355,4 @@
 	private <T> ClassMeta<T> getClassMeta(Class<T> type) {
 		return req.getBeanSession().getClassMeta(type);
 	}
-
-	//------------------------------------------------------------------------------------------------------------------
-	// Static utility methods.
-	//------------------------------------------------------------------------------------------------------------------
-
-	/**
-	 * Utility method that adds path variables to the specified request.
-	 */
-	@SuppressWarnings("unchecked")
-	static HttpServletRequest addPathVars(HttpServletRequest req, Map<String,String> vars) {
-		if (vars != null && ! vars.isEmpty()) {
-			Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR);
-			if (m == null) {
-				m = new TreeMap<>();
-				req.setAttribute(REST_PATHVARS_ATTR, m);
-			}
-			m.putAll(vars);
-		}
-		return req;
-	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
index 4d2dbac..fc79cad 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
@@ -26,6 +26,11 @@
  */
 public class RestCall {
 
+	/**
+	 * Request attribute name for passing path variables from parent to child.
+	 */
+	private static final String REST_PATHVARS_ATTR = "juneau.pathVars";
+
 	private HttpServletRequest req;
 	private HttpServletResponse res;
 	private RestRequest rreq;
@@ -180,6 +185,34 @@
 		return rmethod == null ? null : rmethod.method;
 	}
 
+	/**
+	 * Adds resolved <c><ja>@Resource</ja>(path)</c> variable values to this call.
+	 *
+	 * @param vars The variables to add to this call.
+	 */
+	@SuppressWarnings("unchecked")
+	public void addPathVars(Map<String,String> vars) {
+		if (vars != null && ! vars.isEmpty()) {
+			Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR);
+			if (m == null) {
+				m = new TreeMap<>();
+				req.setAttribute(REST_PATHVARS_ATTR, m);
+			}
+			m.putAll(vars);
+		}
+	}
+
+	/**
+	 * Returns resolved <c><ja>@Resource</ja>(path)</c> variable values on this call.
+	 *
+	 * @return Resolved <c><ja>@Resource</ja>(path)</c> variable values on this call.
+	 */
+	@SuppressWarnings("unchecked")
+	public Map<String,String> getPathVars() {
+		Map<String,String> m = (Map<String,String>)req.getAttribute(REST_PATHVARS_ATTR);
+		return m == null ? Collections.emptyMap() : m;
+	}
+
 	//------------------------------------------------------------------------------------------------------------------
 	// Setters.
 	//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index a376186..f3ead19 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -4861,19 +4861,6 @@
 	}

 

 	/**

-	 * Returns <jk>true</jk> if this resource has any child resources associated with it.

-	 *

-	 * <ul class='seealso'>

-	 * 	<li class='jf'>{@link RestContext#REST_children}

-	 * </ul>

-	 *

-	 * @return <jk>true</jk> if this resource has any child resources associated with it.

-	 */

-	public boolean hasChildResources() {

-		return ! childResources.isEmpty();

-	}

-

-	/**

 	 * Returns the authority path of the resource.

 	 *

 	 * <ul class='seealso'>

@@ -5181,7 +5168,7 @@
 				UrlPathInfo upi2 = new UrlPathInfo(pi == null ? sp : sp + pi);

 				UrlPathPatternMatch uppm = pathPattern.match(upi2);

 				if (uppm != null && ! uppm.hasEmptyVars()) {

-					RequestPath.addPathVars(call.getRequest(), uppm.getVars());

+					call.addPathVars(uppm.getVars());

 					call.request(

 						new OverrideableHttpServletRequest(call.getRequest())

 							.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))

@@ -5195,13 +5182,13 @@
 

 			// If this resource has child resources, try to recursively call them.

 			String pi = call.getPathInfoUndecoded();

-			if (hasChildResources() && pi != null && ! pi.equals("/")) {

+			if ((! childResources.isEmpty()) && pi != null && ! pi.equals("/")) {

 				for (RestContext rc : getChildResources().values()) {

 					UrlPathPattern upp = rc.pathPattern;

 					UrlPathPatternMatch uppm = upp.match(call.getUrlPathInfo());

 					if (uppm != null) {

 						if (! uppm.hasEmptyVars()) {

-							RequestPath.addPathVars(call.getRequest(), uppm.getVars());

+							call.addPathVars(uppm.getVars());

 							HttpServletRequest childRequest = new OverrideableHttpServletRequest(call.getRequest())

 								.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))

 								.servletPath(call.getServletPath() + uppm.getPrefix());

@@ -5219,8 +5206,8 @@
 

 			startCall(call);

 

-			call.restRequest(createRequest(call));

-			call.restResponse(createResponse(call));

+			createRequest(call);

+			createResponse(call);

 

 			StaticFile r = null;

 			if (call.getPathInfoUndecoded() != null) {

diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index eae6bae..72bc3f4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -129,6 +129,7 @@
 		super(call.getRequest());

 		HttpServletRequest req = call.getRequest();

 		this.inner = req;

+		call.restRequest(this);

 		this.context = call.getContext();

 		this.call = call;

 

@@ -183,7 +184,7 @@
 			if (! s.isEmpty())

 				headers.queryParams(queryParams, s);

 

-			this.pathParams = new RequestPath(this);

+			this.pathParams = new RequestPath(call);

 

 		} catch (Exception e) {

 			throw toHttpException(e, InternalServerError.class);

diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index 7642f6a..393b0ff 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -83,6 +83,7 @@
 		super(call.getResponse());

 		this.inner = call.getResponse();

 		this.request = call.getRestRequest();

+		call.restResponse(this);

 		RestContext context = call.getContext();

 

 		for (Map.Entry<String,Object> e : context.getResHeaders().entrySet())