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())