This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new c1433ae JUNEAU-132, JUNEAU-135
c1433ae is described below
commit c1433aeeb39a219fc5dbd517954d4d391494edd5
Author: JamesBognar <[email protected]>
AuthorDate: Thu Sep 5 15:09:44 2019 -0400
JUNEAU-132, JUNEAU-135
Various HTTP parameter annotations should support Optional.
@RestMethod(debug=true) is not causing requests to be logged.
---
.../apache/juneau/oapi/OpenApiParserSession.java | 6 +-
juneau-doc/docs/ReleaseNotes/8.1.1.html | 10 +
.../26.OpenApiDetails/03.Parsers.html | 4 +
.../10.HttpPartAnnotations/01.Body.html | 2 +
.../org/apache/juneau/rest/testutils/ABean.java | 7 +
.../apache/juneau/rest/testutils/TestUtils.java | 2 +-
.../juneau/rest/BasicRestInfoProviderTest.java | 4 +-
.../rest/annotation2/BodyAnnotationTest.java | 57 +++-
.../rest/annotation2/FormDataAnnotationTest.java | 59 +++-
.../rest/annotation2/HeaderAnnotationTest.java | 61 +++-
.../rest/annotation2/PathAnnotationTest.java | 55 +++-
.../annotation2/PathRemainderAnnotationTest.java | 53 ++++
.../rest/annotation2/QueryAnnotationTest.java | 58 +++-
.../apache/juneau/rest/BasicRestCallHandler.java | 198 +++++-------
.../main/java/org/apache/juneau/rest/RestCall.java | 334 +++++++++++++++++++++
.../org/apache/juneau/rest/RestCallHandler.java | 56 ++--
.../org/apache/juneau/rest/RestCallRouter.java | 11 +-
.../java/org/apache/juneau/rest/RestContext.java | 29 +-
.../org/apache/juneau/rest/RestMethodContext.java | 27 +-
.../java/org/apache/juneau/rest/RestRequest.java | 2 +-
20 files changed, 845 insertions(+), 190 deletions(-)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
index 1efadd5..3fc4797 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/oapi/OpenApiParserSession.java
@@ -71,9 +71,13 @@ public class OpenApiParserSession extends UonParserSession {
@Override /* HttpPartParser */
public <T> T parse(HttpPartType partType, HttpPartSchema schema, String
in, ClassMeta<T> type) throws ParseException, SchemaValidationException {
boolean isOptional = type.isOptional();
- while (isOptional)
+
+ while (type != null && type.isOptional())
type = (ClassMeta<T>)type.getElementType();
+ if (type == null)
+ type = (ClassMeta<T>)object();
+
schema = ObjectUtils.firstNonNull(schema, getSchema(),
DEFAULT_SCHEMA);
T t = parseInner(partType, schema, in, type);
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.1.html
b/juneau-doc/docs/ReleaseNotes/8.1.1.html
index 4fcfa34..03addd5 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.1.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.1.html
@@ -27,3 +27,13 @@
Fixed a bug in the parsers where the generic subtype of a
complex bean property type involving both collections and arrays
was not being found. (e.g. <c>List<Long>[]</c>)
</ul>
+
+<h5 class='topic w800'>juneau-rest-server</h5>
+<ul class='spaced-list'>
+ <li>
+ Support for {@link Optional} on method parameters annotated
with {@link oaj.http.annotation.Header}, {@link oaj.http.annotation.FormData},
+ {@link oaj.http.annotation.Query}, {@link
oaj.http.annotation.Path}, and {@link oaj.http.annotation.PathRemainder}.
+ <li>
+ Fixed issue where {@link oajr.annotation.RestRequest#debug()
RestRequest.debug()} annotation wasn't resulting
+ in the HTTP request being logged.
+</ul>
diff --git
a/juneau-doc/docs/Topics/02.juneau-marshall/26.OpenApiDetails/03.Parsers.html
b/juneau-doc/docs/Topics/02.juneau-marshall/26.OpenApiDetails/03.Parsers.html
index e54e6c3..086f0b3 100644
---
a/juneau-doc/docs/Topics/02.juneau-marshall/26.OpenApiDetails/03.Parsers.html
+++
b/juneau-doc/docs/Topics/02.juneau-marshall/26.OpenApiDetails/03.Parsers.html
@@ -216,6 +216,10 @@ OpenAPI Parsers
</tr>
</table>
<p>
+ Additionally, any of the type above can also be wrapped as {@link
java.util.Optional Optionals}.
+</p>
+
+<p>
For arrays, an example of "Any POJO transformable from arrays of the
default types" is:
</p>
<p class='bpcode w800'>
diff --git
a/juneau-doc/docs/Topics/07.juneau-rest-server/10.HttpPartAnnotations/01.Body.html
b/juneau-doc/docs/Topics/07.juneau-rest-server/10.HttpPartAnnotations/01.Body.html
index 082e4e9..37dc260 100644
---
a/juneau-doc/docs/Topics/07.juneau-rest-server/10.HttpPartAnnotations/01.Body.html
+++
b/juneau-doc/docs/Topics/07.juneau-rest-server/10.HttpPartAnnotations/01.Body.html
@@ -98,6 +98,8 @@
<li><c><jk>public static</jk> T
<jsm>forString</jsm>(String in) {...}</c>
</ul>
Note that this also includes all enums.
+ <li>
+ Any {@link java.util.Optional} of anything on this list.
</ol>
<p>
The {@link oaj.oapi.OpenApiSerializer} class can be used to serialize
HTTP bodies to OpenAPI-based output.
diff --git
a/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/ABean.java
b/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/ABean.java
index 439937a..bde2c9a 100644
---
a/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/ABean.java
+++
b/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/ABean.java
@@ -12,6 +12,8 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest.testutils;
+import org.apache.juneau.marshall.*;
+
public class ABean {
public int a;
public String b;
@@ -21,4 +23,9 @@ public class ABean {
this.b = "foo";
return this;
}
+
+ @Override
+ public String toString() {
+ return SimpleJson.DEFAULT.toString(this);
+ }
}
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
b/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
index 10232b0..445204d 100755
---
a/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
+++
b/juneau-rest/juneau-rest-mock-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
@@ -61,7 +61,7 @@ public class TestUtils extends
org.apache.juneau.testutils.TestUtils {
public static Swagger getSwagger(Class<?> c) {
try {
RestContext rc =
RestContext.create(c.newInstance()).build();
- RestRequest req = rc.getCallHandler().createRequest(new
MockServletRequest());
+ RestRequest req = rc.getCallHandler().createRequest(new
RestCall(new MockServletRequest(), null));
RestInfoProvider ip = rc.getInfoProvider();
return ip.getSwagger(req);
} catch (Exception e) {
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
index 8699220..d3cdec8 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
@@ -52,14 +52,14 @@ public class BasicRestInfoProviderTest {
private Swagger getSwaggerWithFile(Object resource) throws Exception {
RestContext rc =
RestContext.create(resource).classpathResourceFinder(TestClasspathResourceFinder.class).build();
- RestRequest req = rc.getCallHandler().createRequest(new
MockServletRequest());
+ RestRequest req = rc.getCallHandler().createRequest(new
RestCall(new MockServletRequest(), null));
RestInfoProvider ip = rc.getInfoProvider();
return ip.getSwagger(req);
}
private static Swagger getSwagger(Object resource) throws Exception {
RestContext rc = RestContext.create(resource).build();
- RestRequest req = rc.getCallHandler().createRequest(new
MockServletRequest());
+ RestRequest req = rc.getCallHandler().createRequest(new
RestCall(new MockServletRequest(), null));
RestInfoProvider ip = rc.getInfoProvider();
return ip.getSwagger(req);
}
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/BodyAnnotationTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/BodyAnnotationTest.java
index c8c4d19..a7476a9 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/BodyAnnotationTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/BodyAnnotationTest.java
@@ -14,7 +14,7 @@ package org.apache.juneau.rest.annotation2;
import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.rest.testutils.TestUtils.*;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import java.io.*;
import java.util.*;
@@ -26,12 +26,14 @@ import org.apache.juneau.http.annotation.Query;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.jsonschema.annotation.*;
+import org.apache.juneau.marshall.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock2.*;
import org.apache.juneau.rest.testutils.*;
import org.apache.juneau.rest.testutils.DTOs;
import org.apache.juneau.uon.*;
import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
import org.junit.*;
import org.junit.runners.*;
@@ -781,6 +783,59 @@ public class BodyAnnotationTest {
i.post("/", "{}").json().execute().assertStatus(200);
}
+
//=================================================================================================================
+ // Optional body parameter.
+
//=================================================================================================================
+
+
@RestResource(serializers=SimpleJsonSerializer.class,parsers=JsonParser.class)
+ public static class J {
+ @RestMethod(name=POST,path="/a")
+ public Object a(@Body Optional<Integer> body) throws Exception {
+ assertNotNull(body);
+ return body;
+ }
+ @RestMethod(name=POST,path="/b")
+ public Object b(@Body Optional<ABean> body) throws Exception {
+ assertNotNull(body);
+ return body;
+ }
+ @RestMethod(name=POST,path="/c")
+ public Object c(@Body Optional<List<ABean>> body) throws
Exception {
+ assertNotNull(body);
+ return body;
+ }
+ @RestMethod(name=POST,path="/d")
+ public Object d(@Body List<Optional<ABean>> body) throws
Exception {
+ return body;
+ }
+ }
+ static MockRest j = MockRest.create(J.class).json().build();;
+
+ @Test
+ public void j01_optionalParam_integer() throws Exception {
+ j.post("/a",
"123").execute().assertStatus(200).assertBody("123");
+ j.post("/a",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void j02_optionalParam_bean() throws Exception {
+ j.post("/b", new
ABean().init()).execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
+ j.post("/b",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void j03_optionalParam_listOfBeans() throws Exception {
+ String body = SimpleJson.DEFAULT.toString(AList.of(new
ABean().init()));
+ j.post("/c",
body).execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ j.post("/c",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void j04_optionalParam_listOfOptionals() throws Exception {
+ String body =
SimpleJson.DEFAULT.toString(AList.of(Optional.of(new ABean().init())));
+ j.post("/d",
body).execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ j.post("/d",
"null").execute().assertStatus(200).assertBody("null");
+ }
//=================================================================================================================
// Swagger - @Body on POJO
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/FormDataAnnotationTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/FormDataAnnotationTest.java
index 86f7c50..f5231a8 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/FormDataAnnotationTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/FormDataAnnotationTest.java
@@ -14,16 +14,18 @@ package org.apache.juneau.rest.annotation2;
import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.rest.testutils.TestUtils.*;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.http.annotation.FormData;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.json.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock2.*;
+import org.apache.juneau.rest.testutils.*;
import org.apache.juneau.urlencoding.*;
import org.junit.*;
import org.junit.runners.*;
@@ -156,6 +158,59 @@ public class FormDataAnnotationTest {
}
//=================================================================================================================
+ // Optional form data parameter.
+
//=================================================================================================================
+
+ @RestResource(serializers=SimpleJsonSerializer.class)
+ public static class D {
+ @RestMethod(name=POST,path="/a")
+ public Object a(@FormData("f1") Optional<Integer> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=POST,path="/b")
+ public Object b(@FormData("f1") Optional<ABean> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=POST,path="/c")
+ public Object c(@FormData("f1") Optional<List<ABean>> f1)
throws Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=POST,path="/d")
+ public Object d(@FormData("f1") List<Optional<ABean>> f1)
throws Exception {
+ return f1;
+ }
+ }
+ static MockRest d =
MockRest.create(D.class).accept("application/json").contentType("application/x-www-form-urlencoded").build();
+
+ @Test
+ public void d01_optionalParam_integer() throws Exception {
+ d.post("/a",
"f1=123").execute().assertStatus(200).assertBody("123");
+ d.post("/a",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void d02_optionalParam_bean() throws Exception {
+ d.post("/b",
"f1=(a=1,b=foo)").execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
+ d.post("/b",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void d03_optionalParam_listOfBeans() throws Exception {
+ d.post("/c",
"f1=@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ d.post("/c",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void d04_optionalParam_listOfOptionals() throws Exception {
+ d.post("/d",
"f1=@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ d.post("/d",
"null").execute().assertStatus(200).assertBody("null");
+ }
+
+
+
//=================================================================================================================
// @FormData on POJO
//=================================================================================================================
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/HeaderAnnotationTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/HeaderAnnotationTest.java
index 35d44a2..40909ab 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/HeaderAnnotationTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/HeaderAnnotationTest.java
@@ -12,14 +12,18 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest.annotation2;
+import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.rest.testutils.TestUtils.*;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import java.util.*;
import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.http.annotation.Header;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.json.*;
import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.mock2.*;
+import org.apache.juneau.rest.testutils.*;
import org.junit.*;
import org.junit.runners.*;
@@ -31,6 +35,59 @@ import org.junit.runners.*;
public class HeaderAnnotationTest {
//=================================================================================================================
+ // Optional header parameter.
+
//=================================================================================================================
+
+ @RestResource(serializers=SimpleJsonSerializer.class)
+ public static class A {
+ @RestMethod(name=GET,path="/a")
+ public Object a(@Header("f1") Optional<Integer> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/b")
+ public Object b(@Header("f1") Optional<ABean> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/c")
+ public Object c(@Header("f1") Optional<List<ABean>> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/d")
+ public Object d(@Header("f1") List<Optional<ABean>> f1) throws
Exception {
+ return f1;
+ }
+ }
+ static MockRest a = MockRest.create(A.class).json().build();
+
+ @Test
+ public void a01_optionalParam_integer() throws Exception {
+ a.get("/a").header("f1",
123).execute().assertStatus(200).assertBody("123");
+ a.get("/a").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void a02_optionalParam_bean() throws Exception {
+ a.get("/b").header("f1",
"(a=1,b=foo)").execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
+ a.get("/b").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void a03_optionalParam_listOfBeans() throws Exception {
+ a.get("/c").header("f1",
"@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ a.get("/c").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void a04_optionalParam_listOfOptionals() throws Exception {
+ a.get("/d").header("f1",
"@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ a.get("/d").execute().assertStatus(200).assertBody("null");
+ }
+
+
+
//=================================================================================================================
// @Header on POJO
//=================================================================================================================
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
index b2c6e1c..a53dd5e 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
@@ -14,17 +14,19 @@ package org.apache.juneau.rest.annotation2;
import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.rest.testutils.TestUtils.*;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.http.annotation.Path;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.json.*;
import org.apache.juneau.marshall.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock2.*;
+import org.apache.juneau.rest.testutils.*;
import org.junit.*;
import org.junit.runners.*;
@@ -677,6 +679,55 @@ public class PathAnnotationTest {
}
//=================================================================================================================
+ // Optional path parameter.
+
//=================================================================================================================
+
+ @RestResource(serializers=SimpleJsonSerializer.class)
+ public static class J {
+ @RestMethod(name=GET,path="/a/{f1}")
+ public Object a(@Path("f1") Optional<Integer> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/b/{f1}")
+ public Object b(@Path("f1") Optional<ABean> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/c/{f1}")
+ public Object c(@Path("f1") Optional<List<ABean>> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/d/{f1}")
+ public Object d(@Path("f1") List<Optional<ABean>> f1) throws
Exception {
+ return f1;
+ }
+ }
+ static MockRest j = MockRest.create(J.class).json().build();
+
+ @Test
+ public void j01_optionalParam_integer() throws Exception {
+ j.get("/a/123").execute().assertStatus(200).assertBody("123");
+ }
+
+ @Test
+ public void j02_optionalParam_bean() throws Exception {
+
j.get("/b/(a=1,b=foo)").execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
+ }
+
+ @Test
+ public void j03_optionalParam_listOfBeans() throws Exception {
+
j.get("/c/@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ }
+
+ @Test
+ public void j04_optionalParam_listOfOptionals() throws Exception {
+
j.get("/d/@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ }
+
+
+
//=================================================================================================================
// @Path on POJO
//=================================================================================================================
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathRemainderAnnotationTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathRemainderAnnotationTest.java
index e4a5c0b..3d64ea7 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathRemainderAnnotationTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathRemainderAnnotationTest.java
@@ -13,10 +13,15 @@
package org.apache.juneau.rest.annotation2;
import static org.apache.juneau.http.HttpMethodName.*;
+import static org.junit.Assert.*;
+
+import java.util.*;
import org.apache.juneau.http.annotation.Path;
+import org.apache.juneau.json.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock2.*;
+import org.apache.juneau.rest.testutils.*;
import org.junit.*;
import org.junit.runners.*;
@@ -52,4 +57,52 @@ public class PathRemainderAnnotationTest {
public void a03_withRemainder2() throws Exception {
a.get("/foo/bar").execute().assertBody("foo/bar");
}
+
+
//=================================================================================================================
+ // Optional path remainer parameter.
+
//=================================================================================================================
+
+ @RestResource(serializers=SimpleJsonSerializer.class)
+ public static class B {
+ @RestMethod(name=GET,path="/a/*")
+ public Object a(@Path("/*") Optional<Integer> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/b/*")
+ public Object b(@Path("/*") Optional<ABean> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/c/*")
+ public Object c(@Path("/*") Optional<List<ABean>> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/d/*")
+ public Object d(@Path("/*") List<Optional<ABean>> f1) throws
Exception {
+ return f1;
+ }
+ }
+ static MockRest b = MockRest.create(B.class).json().build();
+
+ @Test
+ public void b01_optionalParam_integer() throws Exception {
+ b.get("/a/123").execute().assertStatus(200).assertBody("123");
+ }
+
+ @Test
+ public void b02_optionalParam_bean() throws Exception {
+
b.get("/b/(a=1,b=foo)").execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
+ }
+
+ @Test
+ public void b03_optionalParam_listOfBeans() throws Exception {
+
b.get("/c/@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ }
+
+ @Test
+ public void b04_optionalParam_listOfOptionals() throws Exception {
+
b.get("/d/@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ }
}
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/QueryAnnotationTest.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/QueryAnnotationTest.java
index b7a48f9..f34b306 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/QueryAnnotationTest.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/QueryAnnotationTest.java
@@ -14,18 +14,19 @@ package org.apache.juneau.rest.annotation2;
import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.rest.testutils.TestUtils.*;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.http.annotation.Query;
+import org.apache.juneau.http.annotation.*;
import org.apache.juneau.json.*;
import org.apache.juneau.jsonschema.annotation.Items;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock2.*;
+import org.apache.juneau.rest.testutils.*;
import org.junit.*;
import org.junit.runners.*;
@@ -267,6 +268,59 @@ public class QueryAnnotationTest {
}
//=================================================================================================================
+ // Optional query parameter.
+
//=================================================================================================================
+
+ @RestResource(serializers=SimpleJsonSerializer.class)
+ public static class E {
+ @RestMethod(name=GET,path="/a")
+ public Object a(@Query("f1") Optional<Integer> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/b")
+ public Object b(@Query("f1") Optional<ABean> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/c")
+ public Object c(@Query("f1") Optional<List<ABean>> f1) throws
Exception {
+ assertNotNull(f1);
+ return f1;
+ }
+ @RestMethod(name=GET,path="/d")
+ public Object d(@Query("f1") List<Optional<ABean>> f1) throws
Exception {
+ return f1;
+ }
+ }
+ static MockRest e = MockRest.create(E.class).json().build();
+
+ @Test
+ public void e01_optionalParam_integer() throws Exception {
+
e.get("/a?f1=123").execute().assertStatus(200).assertBody("123");
+ e.get("/a").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void e02_optionalParam_bean() throws Exception {
+
e.get("/b?f1=(a=1,b=foo)").execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
+ e.get("/b").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void e03_optionalParam_listOfBeans() throws Exception {
+
e.get("/c?f1=@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ e.get("/c").execute().assertStatus(200).assertBody("null");
+ }
+
+ @Test
+ public void e04_optionalParam_listOfOptionals() throws Exception {
+
e.get("/d?f1=@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
+ e.get("/d").execute().assertStatus(200).assertBody("null");
+ }
+
+
+
//=================================================================================================================
// @Query on POJO
//=================================================================================================================
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
index 65c4c2a..63f407a 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
@@ -54,36 +54,19 @@ public class BasicRestCallHandler implements
RestCallHandler {
this.restCallRouters = context.getCallRouters();
}
- /**
- * Creates a {@link RestRequest} object based on the specified incoming
{@link HttpServletRequest} object.
- *
- * <p>
- * Subclasses may choose to override this method to provide a
specialized request object.
- *
- * @param req The request object from the {@link
#service(HttpServletRequest, HttpServletResponse)} method.
- * @return The wrapped request object.
- * @throws ServletException If any errors occur trying to interpret the
request.
- */
@Override /* RestCallHandler */
- public RestRequest createRequest(HttpServletRequest req) throws
ServletException {
- return new RestRequest(context, req);
+ public RestCall createCall(HttpServletRequest req, HttpServletResponse
res) {
+ return new RestCall(req,
res).logger(context.getCallLogger()).loggerConfig(context.getCallLoggerConfig());
+ }
+
+ @Override /* RestCallHandler */
+ public RestRequest createRequest(RestCall call) throws ServletException
{
+ return new RestRequest(context, call.getRequest());
}
- /**
- * Creates a {@link RestResponse} object based on the specified
incoming {@link HttpServletResponse} object
- * and the request returned by {@link
#createRequest(HttpServletRequest)}.
- *
- * <p>
- * Subclasses may choose to override this method to provide a
specialized response object.
- *
- * @param req The request object returned by {@link
#createRequest(HttpServletRequest)}.
- * @param res The response object from the {@link
#service(HttpServletRequest, HttpServletResponse)} method.
- * @return The wrapped response object.
- * @throws ServletException If any errors occur trying to interpret the
request or response.
- */
@Override /* RestCallHandler */
- public RestResponse createResponse(RestRequest req, HttpServletResponse
res) throws ServletException {
- return new RestResponse(context, req, res);
+ public RestResponse createResponse(RestCall call) throws
ServletException {
+ return new RestResponse(context, call.getRestRequest(),
call.getResponse());
}
/**
@@ -100,112 +83,94 @@ public class BasicRestCallHandler implements
RestCallHandler {
@Override /* RestCallHandler */
public void service(HttpServletRequest r1, HttpServletResponse r2)
throws ServletException, IOException {
- long startTime = System.currentTimeMillis();
- RestRequest req = null;
- RestResponse res = null;
- RestCallLogger logger = context.getCallLogger();
- RestCallLoggerConfig loggerConfig =
context.getCallLoggerConfig();
+ RestCall call = createCall(r1, r2);
try {
context.checkForInitException();
- String pathInfo = RestUtils.getPathInfoUndecoded(r1);
// Can't use r1.getPathInfo() because we don't want '%2F' resolved.
- UrlPathInfo upi = new UrlPathInfo(pathInfo);
-
// If the resource path contains variables (e.g.
@RestResource(path="/f/{a}/{b}"), then we want to resolve
// those variables and push the servletPath to include
the resolved variables. The new pathInfo will be
// the remainder after the new servletPath.
// Only do this for the top-level resource because the
logic for child resources are processed next.
if (context.pathPattern.hasVars() &&
context.getParentContext() == null) {
- String sp = r1.getServletPath();
- UrlPathInfo upi2 = new UrlPathInfo(pathInfo ==
null ? sp : sp + pathInfo);
+ String sp = call.getServletPath();
+ String pi = call.getPathInfoUndecoded();
+ UrlPathInfo upi2 = new UrlPathInfo(pi == null ?
sp : sp + pi);
UrlPathPatternMatch uppm =
context.pathPattern.match(upi2);
if (uppm != null && ! uppm.hasEmptyVars()) {
- RequestPath.addPathVars(r1,
uppm.getVars());
- r1 = new
OverrideableHttpServletRequest(r1)
-
.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
- .servletPath(uppm.getPrefix());
- pathInfo =
RestUtils.getPathInfoUndecoded(r1); // Can't use r1.getPathInfo() because we
don't want '%2F' resolved.
- upi = new UrlPathInfo(pathInfo);
+
RequestPath.addPathVars(call.getRequest(), uppm.getVars());
+ call.request(
+ new
OverrideableHttpServletRequest(call.getRequest())
+
.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
+
.servletPath(uppm.getPrefix())
+ );
} else {
- if (isDebug(r1)) {
- r1 =
CachingHttpServletRequest.wrap(r1);
- r1.setAttribute("Debug", true);
- }
- r2.setStatus(SC_NOT_FOUND);
+
call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
return;
}
}
// If this resource has child resources, try to
recursively call them.
- if (context.hasChildResources() && pathInfo != null &&
! pathInfo.equals("/")) {
+ String pi = call.getPathInfoUndecoded();
+ if (context.hasChildResources() && pi != null && !
pi.equals("/")) {
for (RestContext rc :
context.getChildResources().values()) {
UrlPathPattern upp = rc.pathPattern;
- UrlPathPatternMatch uppm =
upp.match(upi);
+ UrlPathPatternMatch uppm =
upp.match(call.getUrlPathInfo());
if (uppm != null) {
if (! uppm.hasEmptyVars()) {
-
RequestPath.addPathVars(r1, uppm.getVars());
- HttpServletRequest
childRequest = new OverrideableHttpServletRequest(r1)
+
RequestPath.addPathVars(call.getRequest(), uppm.getVars());
+ HttpServletRequest
childRequest = new OverrideableHttpServletRequest(call.getRequest())
.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
-
.servletPath(r1.getServletPath() + uppm.getPrefix());
-
rc.getCallHandler().service(childRequest, r2);
+
.servletPath(call.getServletPath() + uppm.getPrefix());
+
rc.getCallHandler().service(childRequest, call.getResponse());
} else {
- if (isDebug(r1)) {
- r1 =
CachingHttpServletRequest.wrap(r1);
-
r1.setAttribute("Debug", true);
- }
-
r2.setStatus(SC_NOT_FOUND);
+
call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
}
return;
}
}
}
- if (isDebug(r1)) {
- r1 = CachingHttpServletRequest.wrap(r1);
- r2 = CachingHttpServletResponse.wrap(r2);
- r1.setAttribute("Debug", true);
- }
+ call.debug(isDebug(call));
- context.startCall(r1, r2);
+ context.startCall(call);
- req = createRequest(r1);
- res = createResponse(req, r2);
- req.setResponse(res);
- context.setRequest(req);
- context.setResponse(res);
- String method = req.getMethod();
- String methodUC = method.toUpperCase(Locale.ENGLISH);
+ call.restRequest(createRequest(call));
+ call.restResponse(createResponse(call));
+
+ context.setRequest(call.getRestRequest());
+ context.setResponse(call.getRestResponse());
StreamResource r = null;
- if (pathInfo != null) {
- String p = pathInfo.substring(1);
+ if (call.getPathInfoUndecoded() != null) {
+ String p =
call.getPathInfoUndecoded().substring(1);
if (context.isStaticFile(p)) {
StaticFile sf =
context.resolveStaticFile(p);
r = sf.resource;
- res.setResponseMeta(sf.meta);
+ call.responseMeta(sf.meta);
} else if (p.equals("favicon.ico")) {
- res.setOutput(null);
+ call.output(null);
}
}
if (r != null) {
- res.setStatus(SC_OK);
- res.setOutput(r);
+ call.status(SC_OK);
+ call.output(r);
} else {
// If the specified method has been defined in
a subclass, invoke it.
int rc = 0;
- if (restCallRouters.containsKey(methodUC)) {
- rc =
restCallRouters.get(methodUC).invoke(upi, req, res);
+ String m = call.getMethod();
+ if (restCallRouters.containsKey(m)) {
+ rc =
restCallRouters.get(m).invoke(call);
} else if (restCallRouters.containsKey("*")) {
- rc =
restCallRouters.get("*").invoke(upi, req, res);
+ rc =
restCallRouters.get("*").invoke(call);
}
// Should be 405 if the URL pattern matched but
HTTP method did not.
if (rc == 0)
for (RestCallRouter rcc :
restCallRouters.values())
- if (rcc.matches(upi))
+ if (rcc.matches(call))
rc =
SC_METHOD_NOT_ALLOWED;
// Should be 404 if URL pattern didn't match.
@@ -214,54 +179,36 @@ public class BasicRestCallHandler implements
RestCallHandler {
// If not invoked above, see if it's an OPTIONs
request
if (rc != SC_OK)
- handleNotFound(rc, req, res);
+ handleNotFound(call.status(rc));
- if (res.getStatus() == 0)
- res.setStatus(rc);
+ if (call.getStatus() == 0)
+ call.status(rc);
}
- if (res.hasOutput()) {
+ if (call.hasOutput()) {
// Now serialize the output if there was any.
// Some subclasses may write to the
OutputStream or Writer directly.
- handleResponse(req, res);
+ handleResponse(call);
}
- // Make sure our writer in RestResponse gets written.
- res.flushBuffer();
- req.close();
-
- r1 = req.getInner();
- r2 = res.getInner();
- loggerConfig = req.getCallLoggerConfig();
-
- r1.setAttribute("ExecTime", System.currentTimeMillis()
- startTime);
} catch (Throwable e) {
- e = convertThrowable(e);
- if (req != null) {
- r1 = req.getInner();
- loggerConfig = req.getCallLoggerConfig();
- }
- if (res != null)
- r2 = res.getInner();
- r1.setAttribute("Exception", e);
- r1.setAttribute("ExecTime", System.currentTimeMillis()
- startTime);
- handleError(r1, r2, e);
+ handleError(call, convertThrowable(e));
} finally {
context.clearState();
}
- logger.log(loggerConfig, r1, r2);
- context.finishCall(r1, r2);
+ call.finish();
+ context.finishCall(call);
}
- private boolean isDebug(HttpServletRequest req) {
+ private boolean isDebug(RestCall call) {
Enablement e = context.getDebug();
if (e == TRUE)
return true;
if (e == FALSE)
return false;
- return "true".equalsIgnoreCase(req.getHeader("X-Debug"));
+ return
"true".equalsIgnoreCase(call.getRequest().getHeader("X-Debug"));
}
/**
@@ -274,20 +221,24 @@ public class BasicRestCallHandler implements
RestCallHandler {
*
* <p>
* The default implementation simply iterates through the response
handlers on this resource
- * looking for the first one whose {@link
ResponseHandler#handle(RestRequest, RestResponse)} method returns
+ * looking for the first one whose {@link
ResponseHandler#handle(RestRequest,RestResponse)} method returns
* <jk>true</jk>.
*
- * @param req The HTTP request.
- * @param res The HTTP response.
+ * @param call The HTTP call.
* @throws IOException Thrown by underlying stream.
* @throws RestException Non-200 response.
*/
@Override /* RestCallHandler */
- public void handleResponse(RestRequest req, RestResponse res) throws
IOException, RestException, NotImplemented {
+ public void handleResponse(RestCall call) throws IOException,
RestException, NotImplemented {
+
+ RestRequest req = call.getRestRequest();
+ RestResponse res = call.getRestResponse();
+
// Loop until we find the correct handler for the POJO.
for (ResponseHandler h : context.getResponseHandlers())
if (h.handle(req, res))
return;
+
Object output = res.getOutput();
throw new NotImplemented("No response handlers found to process
output of type '"+(output == null ? null : output.getClass().getName())+"'");
}
@@ -325,14 +276,13 @@ public class BasicRestCallHandler implements
RestCallHandler {
* Subclasses can override this method to provide a 2nd-chance for
specifying a response.
* The default implementation will simply throw an exception with an
appropriate message.
*
- * @param rc The HTTP response code.
- * @param req The HTTP request.
- * @param res The HTTP response.
+ * @param call The HTTP call.
*/
@Override /* RestCallHandler */
- public void handleNotFound(int rc, RestRequest req, RestResponse res)
throws Exception {
- String pathInfo = req.getPathInfo();
- String methodUC = req.getMethod();
+ public void handleNotFound(RestCall call) throws Exception {
+ String pathInfo = call.getPathInfo();
+ String methodUC = call.getMethod();
+ int rc = call.getStatus();
String onPath = pathInfo == null ? " on no pathInfo" :
String.format(" on path '%s'", pathInfo);
if (rc == SC_NOT_FOUND)
throw new NotFound("Method ''{0}'' not found on
resource with matching pattern{1}.", methodUC, onPath);
@@ -350,18 +300,22 @@ public class BasicRestCallHandler implements
RestCallHandler {
* <p>
* Subclasses can override this method to provide their own custom
error response handling.
*
- * @param req The servlet request.
- * @param res The servlet response.
+ * @param call The rest call.
* @param e The exception that occurred.
* @throws IOException Can be thrown if a problem occurred trying to
write to the output stream.
*/
@Override /* RestCallHandler */
@SuppressWarnings("deprecation")
- public synchronized void handleError(HttpServletRequest req,
HttpServletResponse res, Throwable e) throws IOException {
+ public synchronized void handleError(RestCall call, Throwable e) throws
IOException {
+
+ call.exception(e);
int occurrence = context == null ? 0 :
context.getStackTraceOccurrence(e);
RestException e2 = (e instanceof RestException ?
(RestException)e : new RestException(e, 500)).setOccurrence(occurrence);
+ HttpServletRequest req = call.getRequest();
+ HttpServletResponse res = call.getResponse();
+
Throwable t = e2.getRootCause();
if (t != null) {
res.setHeader("Exception-Name", t.getClass().getName());
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
new file mode 100644
index 0000000..b43afbf
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
@@ -0,0 +1,334 @@
+//
***************************************************************************************************************************
+// * 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;
+
+import java.io.*;
+import java.util.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.httppart.bean.*;
+import org.apache.juneau.rest.util.*;
+
+/**
+ * A wrapper around a single HttpServletRequest/HttpServletResponse pair.
+ */
+public class RestCall {
+
+ private HttpServletRequest req;
+ private HttpServletResponse res;
+ private RestRequest rreq;
+ private RestResponse rres;
+ private UrlPathInfo urlPathInfo;
+ private String pathInfoUndecoded;
+ private long startTime = System.currentTimeMillis();
+ private RestCallLogger logger;
+ private RestCallLoggerConfig loggerConfig;
+
+ /**
+ * Constructor.
+ *
+ * @param req The incoming HTTP servlet request object.
+ * @param res The incoming HTTP servlet response object.
+ */
+ public RestCall(HttpServletRequest req, HttpServletResponse res) {
+ request(req).response(res);
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Request/response objects.
+
//------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Overrides the request object on the REST call.
+ *
+ * @param req The new HTTP servlet request.
+ * @return This object (for method chaining).
+ */
+ public RestCall request(HttpServletRequest req) {
+ this.req = req;
+ this.urlPathInfo = null;
+ this.pathInfoUndecoded = null;
+ return this;
+ }
+
+ /**
+ * Overrides the response object on the REST call.
+ *
+ * @param res The new HTTP servlet response.
+ * @return This object (for method chaining).
+ */
+ public RestCall response(HttpServletResponse res) {
+ this.res = res;
+ return this;
+ }
+
+ /**
+ * Set the {@link RestRequest} object on this REST call.
+ *
+ * @param rreq The {@link RestRequest} object on this REST call.
+ * @return This object (for method chaining).
+ */
+ public RestCall restRequest(RestRequest rreq) {
+ request(rreq);
+ this.rreq = rreq;
+ return this;
+ }
+
+ /**
+ * Set the {@link RestResponse} object on this REST call.
+ *
+ * @param rres The {@link RestResponse} object on this REST call.
+ * @return This object (for method chaining).
+ */
+ public RestCall restResponse(RestResponse rres) {
+ response(rres);
+ this.rres = rres;
+ this.rreq.setResponse(rres);
+ return this;
+ }
+
+ /**
+ * Returns the HTTP servlet request of this REST call.
+ *
+ * @return the HTTP servlet request of this REST call.
+ */
+ public HttpServletRequest getRequest() {
+ return req;
+ }
+
+ /**
+ * Returns the HTTP servlet response of this REST call.
+ *
+ * @return the HTTP servlet response of this REST call.
+ */
+ public HttpServletResponse getResponse() {
+ return res;
+ }
+
+ /**
+ * Returns the REST request of this REST call.
+ *
+ * @return the REST request of this REST call.
+ */
+ public RestRequest getRestRequest() {
+ return rreq;
+ }
+
+ /**
+ * Returns the REST response of this REST call.
+ *
+ * @return the REST response of this REST call.
+ */
+ public RestResponse getRestResponse() {
+ return rres;
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Setters.
+
//------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Sets the logger to use when logging this call.
+ *
+ * @param logger The logger to use when logging this call.
+ * @return This object (for method chaining).
+ */
+ public RestCall logger(RestCallLogger logger) {
+ this.logger = logger;
+ return this;
+ }
+
+ /**
+ * Sets the logging configuration to use when logging this call.
+ *
+ * @param config The logging configuration to use when logging this
call.
+ * @return This object (for method chaining).
+ */
+ public RestCall loggerConfig(RestCallLoggerConfig config) {
+ this.loggerConfig = config;
+ return this;
+ }
+
+ /**
+ * Enables or disabled debug mode on this call.
+ *
+ * @param b The debug flag value.
+ * @return This object (for method chaining).
+ * @throws IOException Occurs if request body could not be cached into
memory.
+ */
+ public RestCall debug(boolean b) throws IOException {
+ if (b) {
+ req = CachingHttpServletRequest.wrap(req);
+ res = CachingHttpServletResponse.wrap(res);
+ }
+ req.setAttribute("Debug", b);
+ return this;
+ }
+
+ /**
+ * Sets the HTTP status on this call.
+ *
+ * @param code The status code.
+ * @return This object (for method chaining).
+ */
+ public RestCall status(int code) {
+ res.setStatus(code);
+ return this;
+ }
+
+ /**
+ * Identifies that an exception occurred during this call.
+ *
+ * @param e The thrown exception.
+ * @return This object (for method chaining).
+ */
+ public RestCall exception(Throwable e) {
+ req.setAttribute("Exception", e);
+ return this;
+ }
+
+ /**
+ * Sets metadata about the response.
+ *
+ * @param meta The metadata about the response.
+ * @return This object (for method chaining).
+ */
+ public RestCall responseMeta(ResponseBeanMeta meta) {
+ if (rres != null)
+ rres.setResponseMeta(meta);
+ return this;
+ }
+
+ /**
+ * Sets the output object to serialize as the response of this call.
+ *
+ * @param output The response output POJO.
+ * @return This object (for method chaining).
+ */
+ public RestCall output(Object output) {
+ if (rres != null)
+ rres.setOutput(output);
+ return this;
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Lifecycle methods.
+
//------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Called at the end of a call to finish any remaining tasks such as
flushing buffers and logging the response.
+ *
+ * @return This object (for method chaining).
+ */
+ public RestCall finish() {
+ try {
+ res.flushBuffer();
+ req.setAttribute("ExecTime", System.currentTimeMillis()
- startTime);
+ if (rreq != null)
+ rreq.close();
+ } catch (Exception e) {
+ exception(e);
+ }
+ if (logger != null)
+ logger.log(loggerConfig, req, res);
+ return this;
+ }
+
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Pass-through convenience methods.
+
//------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Shortcut for calling <c>getRequest().getServletPath()</c>.
+ *
+ * @return The request servlet path.
+ */
+ public String getServletPath() {
+ return req.getServletPath();
+ }
+
+ /**
+ * Returns the request path info as a {@link UrlPathInfo} bean.
+ *
+ * @return The request path info as a {@link UrlPathInfo} bean.
+ */
+ public UrlPathInfo getUrlPathInfo() {
+ if (urlPathInfo == null)
+ urlPathInfo = new UrlPathInfo(getPathInfoUndecoded());
+ return urlPathInfo;
+ }
+
+ /**
+ * Shortcut for calling <c>getRequest().getPathInfo()</c>.
+ *
+ * @return The request servlet path info.
+ */
+ public String getPathInfo() {
+ return req.getPathInfo();
+ }
+
+ /**
+ * Same as {@link #getPathInfo()} but doesn't decode encoded characters.
+ *
+ * @return The undecoded request servlet path info.
+ */
+ public String getPathInfoUndecoded() {
+ if (pathInfoUndecoded == null)
+ pathInfoUndecoded = RestUtils.getPathInfoUndecoded(req);
+ return pathInfoUndecoded;
+ }
+
+ /**
+ * Returns the HTTP method name.
+ *
+ * @return The HTTP method name, always uppercased.
+ */
+ public String getMethod() {
+ if (rreq != null)
+ return rreq.getMethod().toUpperCase(Locale.ENGLISH);
+ return req.getMethod().toUpperCase(Locale.ENGLISH);
+ }
+
+ /**
+ * Shortcut for calling <c>getRequest().getStatus()</c>.
+ *
+ * @return The response status code.
+ */
+ public int getStatus() {
+ return res.getStatus();
+ }
+
+ /**
+ * Shortcut for calling <c>getRestResponse().hasOutput()</c>.
+ *
+ * @return <jk>true</jk> if response has output.
+ */
+ public boolean hasOutput() {
+ if (rres != null)
+ return rres.hasOutput();
+ return false;
+ }
+
+ /**
+ * Shortcut for calling <c>getRestResponse().getOutput()</c>.
+ *
+ * @return The response output.
+ */
+ public Object getOutput() {
+ if (rres != null)
+ return rres.getOutput();
+ return null;
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
index 7587db5..6946f12 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
@@ -38,64 +38,68 @@ public interface RestCallHandler {
public interface Null extends RestCallHandler {}
/**
+ * The main service method.
+ *
+ * @param r1 The incoming HTTP servlet request object.
+ * @param r2 The incoming HTTP servlet response object.
+ * @throws ServletException Error occurred.
+ * @throws IOException Thrown by underlying stream.
+ */
+ public void service(HttpServletRequest r1, HttpServletResponse r2)
throws ServletException, IOException;
+
+ /**
+ * Wraps an incoming servlet request/response pair into a single {@link
RestCall} object.
+ *
+ * @param req The rest request.
+ * @param res The rest response.
+ * @return The wrapped request/response pair.
+ */
+ public RestCall createCall(HttpServletRequest req, HttpServletResponse
res);
+
+ /**
* Creates a {@link RestRequest} object based on the specified incoming
{@link HttpServletRequest} object.
*
- * @param req The request object from the {@link
#service(HttpServletRequest, HttpServletResponse)} method.
+ * @param call The current REST call.
* @return The wrapped request object.
* @throws ServletException If any errors occur trying to interpret the
request.
*/
- public RestRequest createRequest(HttpServletRequest req) throws
ServletException;
+ public RestRequest createRequest(RestCall call) throws ServletException;
/**
* Creates a {@link RestResponse} object based on the specified
incoming {@link HttpServletResponse} object
- * and the request returned by {@link
#createRequest(HttpServletRequest)}.
+ * and the request returned by {@link #createRequest(RestCall)}.
*
- * @param req The request object returned by {@link
#createRequest(HttpServletRequest)}.
- * @param res The response object from the {@link
#service(HttpServletRequest, HttpServletResponse)} method.
+ * @param call The current REST call.
* @return The wrapped response object.
* @throws ServletException If any errors occur trying to interpret the
request or response.
*/
- public RestResponse createResponse(RestRequest req, HttpServletResponse
res) throws ServletException;
-
- /**
- * The main service method.
- *
- * @param r1 The incoming HTTP servlet request object.
- * @param r2 The incoming HTTP servlet response object.
- * @throws ServletException Error occurred.
- * @throws IOException Thrown by underlying stream.
- */
- public void service(HttpServletRequest r1, HttpServletResponse r2)
throws ServletException, IOException;
+ public RestResponse createResponse(RestCall call) throws
ServletException;
/**
* The main method for serializing POJOs passed in through the {@link
RestResponse#setOutput(Object)} method or
* returned by the Java method.
*
- * @param req The HTTP request.
- * @param res The HTTP response.
+ * @param call The current REST call.
* @throws Exception Can be thrown if error occurred while handling
response.
*/
- public void handleResponse(RestRequest req, RestResponse res) throws
Exception;
+ public void handleResponse(RestCall call) throws Exception;
/**
* Handle the case where a matching method was not found.
*
- * @param rc The HTTP response code.
- * @param req The HTTP request.
- * @param res The HTTP response.
+ * @param call The current REST call.
* @throws Exception Can be thrown if error occurred while handling
response.
*/
- public void handleNotFound(int rc, RestRequest req, RestResponse res)
throws Exception;
+ public void handleNotFound(RestCall call) throws Exception;
/**
* Method for handling response errors.
*
- * @param req The servlet request.
- * @param res The servlet response.
+ * @param call The current REST call.
* @param e The exception that occurred.
* @throws Exception Can be thrown if error occurred while handling
response.
*/
- public void handleError(HttpServletRequest req, HttpServletResponse
res, Throwable e) throws Exception;
+ public void handleError(RestCall call, Throwable e) throws Exception;
/**
* Method for converting thrown exceptions into other types before they
are handled.
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
index 58e0c63..6aa2fe3 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
@@ -68,9 +68,10 @@ public class RestCallRouter {
}
}
- boolean matches(UrlPathInfo pathInfo) {
+ boolean matches(RestCall call) {
+ UrlPathInfo pi = call.getUrlPathInfo();
for (RestMethodContext m : restJavaMethods)
- if (m.matches(pathInfo))
+ if (m.matches(pi))
return true;
return false;
}
@@ -84,13 +85,13 @@ public class RestCallRouter {
* @param pathInfo The value of {@link
HttpServletRequest#getPathInfo()} (sorta)
* @return The HTTP response code.
*/
- int invoke(UrlPathInfo pathInfo, RestRequest req, RestResponse res)
throws Throwable {
+ int invoke(RestCall call) throws Throwable {
if (restJavaMethods.length == 1)
- return restJavaMethods[0].invoke(pathInfo, req, res);
+ return restJavaMethods[0].invoke(call);
int maxRc = 0;
for (RestMethodContext m : restJavaMethods) {
- int rc = m.invoke(pathInfo, req, res);
+ int rc = m.invoke(call);
if (rc == SC_OK)
return SC_OK;
maxRc = Math.max(maxRc, rc);
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 6d0a2e3..57cb7b2 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
@@ -2297,7 +2297,7 @@ public final class RestContext extends BeanContext {
* <ul>
* <li class='jm'>{@link
RestContext#isRenderResponseStackTraces()
RestContext.isRenderResponseStackTraces()}
* </ul>
- * That method is used by {@link
BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse,
Throwable)}.
+ * That method is used by {@link
BasicRestCallHandler#handleError(RestCall, Throwable)}.
* </ul>
*/
public static final String REST_renderResponseStackTraces = PREFIX +
".renderResponseStackTraces.b";
@@ -3085,7 +3085,7 @@ public final class RestContext extends BeanContext {
* Affects the following methods:
* <ul class='javatree'>
* <li class='jm'>{@link
RestContext#getStackTraceOccurrence(Throwable)
RestContext.getStackTraceOccurrance(Throwable)}
- * <li class='jm'>{@link
RestCallHandler#handleError(HttpServletRequest, HttpServletResponse, Throwable)}
+ * <li class='jm'>{@link RestCallHandler#handleError(RestCall,
Throwable)}
* <li class='jm'>{@link RestException#getOccurrence()} - Returns
the number of times this exception occurred.
* </ul>
*
@@ -3763,20 +3763,20 @@ public final class RestContext extends BeanContext {
sm = new
RestMethodContext(smb) {
@Override
- int
invoke(UrlPathInfo pathInfo, RestRequest req, RestResponse res) throws
Throwable {
+ int
invoke(RestCall call) throws Throwable {
- int rc
= super.invoke(pathInfo, req, res);
+ int rc
= super.invoke(call);
if (rc
!= SC_OK)
return rc;
- final
Object o = res.getOutput();
+ final
Object o = call.getOutput();
- if
("GET".equals(req.getMethod())) {
-
res.setOutput(rim.getMethodsByPath().keySet());
+ if
("GET".equals(call.getMethod())) {
+
call.output(rim.getMethodsByPath().keySet());
return SC_OK;
- } else
if ("POST".equals(req.getMethod())) {
-
String pip = pathInfo.getPath();
+ } else
if ("POST".equals(call.getMethod())) {
+
String pip = call.getUrlPathInfo().getPath();
if (pip.indexOf('/') != -1)
pip = pip.substring(pip.lastIndexOf('/')+1);
pip = urlDecode(pip);
@@ -3784,6 +3784,7 @@ public final class RestContext extends BeanContext {
if (rmm != null) {
Method m = rmm.getJavaMethod();
try {
+
RestRequest req = call.getRestRequest();
// Parse the args and invoke the method.
Parser p = req.getBody().getParser();
Object[] args = null;
@@ -3795,7 +3796,7 @@ public final class RestContext extends BeanContext {
}
}
Object output = m.invoke(o, args);
-
res.setOutput(output);
+
call.output(output);
return SC_OK;
} catch (Exception e) {
throw new InternalServerError(e);
@@ -5099,17 +5100,17 @@ public final class RestContext extends BeanContext {
/*
* Calls all @RestHook(START) methods.
*/
- void startCall(HttpServletRequest req, HttpServletResponse res) {
+ void startCall(RestCall call) {
for (int i = 0; i < startCallMethods.length; i++)
- startOrFinish(resource, startCallMethods[i],
startCallMethodParams[i], req, res);
+ startOrFinish(resource, startCallMethods[i],
startCallMethodParams[i], call.getRequest(), call.getResponse());
}
/*
* Calls all @RestHook(FINISH) methods.
*/
- void finishCall(HttpServletRequest req, HttpServletResponse res) {
+ void finishCall(RestCall call) {
for (int i = 0; i < endCallMethods.length; i++)
- startOrFinish(resource, endCallMethods[i],
endCallMethodParams[i], req, res);
+ startOrFinish(resource, endCallMethods[i],
endCallMethodParams[i], call.getRequest(), call.getResponse());
}
private static void startOrFinish(Object resource, Method m, Class<?>[]
p, HttpServletRequest req, HttpServletResponse res) throws RestException,
InternalServerError {
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
index 5a8bd06..aed5bd0 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
@@ -687,13 +687,17 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
this.debug = getInstanceProperty(RESTMETHOD_debug,
Enablement.class, context.getDebug());
- Object clc = getProperty(RESTMETHOD_callLoggerConfig);
- if (clc instanceof RestCallLoggerConfig)
- this.callLoggerConfig = (RestCallLoggerConfig)clc;
- else if (clc instanceof ObjectMap)
- this.callLoggerConfig =
RestCallLoggerConfig.create().parent(context.getCallLoggerConfig()).apply((ObjectMap)clc).build();
- else
- this.callLoggerConfig = context.getCallLoggerConfig();
+ if (debug == Enablement.TRUE) {
+ this.callLoggerConfig =
RestCallLoggerConfig.DEFAULT_DEBUG;
+ } else {
+ Object clc = getProperty(RESTMETHOD_callLoggerConfig);
+ if (clc instanceof RestCallLoggerConfig)
+ this.callLoggerConfig =
(RestCallLoggerConfig)clc;
+ else if (clc instanceof ObjectMap)
+ this.callLoggerConfig =
RestCallLoggerConfig.create().parent(context.getCallLoggerConfig()).apply((ObjectMap)clc).build();
+ else
+ this.callLoggerConfig =
context.getCallLoggerConfig();
+ }
}
ResponseBeanMeta getResponseBeanMeta(Object o) {
@@ -797,12 +801,15 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
* @param pathInfo The value of {@link
HttpServletRequest#getPathInfo()} (sorta)
* @return The HTTP response code.
*/
- int invoke(UrlPathInfo pathInfo, RestRequest req, RestResponse res)
throws Throwable {
+ int invoke(RestCall call) throws Throwable {
- UrlPathPatternMatch pm = pathPattern.match(pathInfo);
+ UrlPathPatternMatch pm =
pathPattern.match(call.getUrlPathInfo());
if (pm == null)
return SC_NOT_FOUND;
+ RestRequest req = call.getRestRequest();
+ RestResponse res = call.getRestResponse();
+
RequestPath rp = req.getPathMatch();
for (Map.Entry<String,String> e : pm.getVars().entrySet())
rp.put(e.getKey(), e.getValue());
@@ -829,6 +836,8 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
context.preCall(req, res);
+
call.debug(req.isDebug()).loggerConfig(req.getCallLoggerConfig());
+
Object[] args = new Object[methodParams.length];
for (int i = 0; i < methodParams.length; i++) {
try {
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 78d9919..054a89c 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
@@ -1322,7 +1322,7 @@ public final class RestRequest extends
HttpServletRequestWrapper {
Boolean b = ObjectUtils.castOrNull(getAttribute("Debug"),
Boolean.class);
if (b != null)
return b;
- Enablement e = context.getDebug();
+ Enablement e = restJavaMethod != null ?
restJavaMethod.getDebug() : context.getDebug();
if (e == TRUE)
return true;
if (e == FALSE)