JUNEAU-132, JUNEAU-135
Various HTTP parameter annotations should support Optional.
@RestMethod(debug=true) is not causing requests to be logged.
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 @@
@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 @@
</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 @@
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 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 @@
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 @@
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.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 @@
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 @@
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 @@
}
//=================================================================================================================
+ // 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 @@
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 @@
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 @@
}
//=================================================================================================================
+ // 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 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 @@
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 @@
}
//=================================================================================================================
+ // 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 @@
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());
}
- /**
- * 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 RestRequest createRequest(RestCall call) throws ServletException {
+ return new RestRequest(context, call.getRequest());
+ }
+
+ @Override /* RestCallHandler */
+ public RestResponse createResponse(RestCall call) throws ServletException {
+ return new RestResponse(context, call.getRestRequest(), call.getResponse());
}
/**
@@ -100,112 +83,94 @@
@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 @@
// 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 @@
*
* <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 @@
* 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 @@
* <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,26 +38,6 @@
public interface Null extends RestCallHandler {}
/**
- * 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.
- * @return The wrapped request object.
- * @throws ServletException If any errors occur trying to interpret the request.
- */
- public RestRequest createRequest(HttpServletRequest req) throws ServletException;
-
- /**
- * Creates a {@link RestResponse} object based on the specified incoming {@link HttpServletResponse} object
- * and the request returned by {@link #createRequest(HttpServletRequest)}.
- *
- * @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.
- */
- public RestResponse createResponse(RestRequest req, HttpServletResponse res) throws ServletException;
-
- /**
* The main service method.
*
* @param r1 The incoming HTTP servlet request object.
@@ -68,34 +48,58 @@
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 call The current REST call.
+ * @return The wrapped request object.
+ * @throws ServletException If any errors occur trying to interpret the request.
+ */
+ 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(RestCall)}.
+ *
+ * @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(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 @@
}
}
- 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 @@
* @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 @@
* <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 @@
* 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 @@
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 @@
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 @@
}
}
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 @@
/*
* 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 @@
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 @@
* @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 @@
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 @@
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)