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&lt;Long&gt;[]</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)

Reply via email to