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 8e408b5  Rework Response API.
8e408b5 is described below

commit 8e408b5270ab40cc7f69a4aaa69e88c530821339
Author: JamesBognar <jamesbog...@apache.org>
AuthorDate: Mon Jul 30 09:26:34 2018 -0400

    Rework Response API.
---
 .../org/apache/juneau/httppart/ResponseMeta.java   | 231 +++++++++++++++++++++
 .../juneau/httppart/ResponsePropertyMeta.java      | 136 ++++++++++++
 .../apache/juneau/rest/BasicRestCallHandler.java   |  42 ++--
 .../org/apache/juneau/rest/ResponseHandler.java    |   2 +-
 .../MovedPermanently.java => ResponseObject.java}  |  82 +++++---
 .../PermanentRedirect.java => ResponsePart.java}   |  69 +++---
 .../org/apache/juneau/rest/RestCallHandler.java    |   7 +-
 .../java/org/apache/juneau/rest/RestContext.java   |  31 ++-
 .../org/apache/juneau/rest/RestJavaMethod.java     |  36 +++-
 .../org/apache/juneau/rest/RestMethodReturn.java   |  29 +--
 .../org/apache/juneau/rest/RestMethodThrown.java   |  29 +--
 .../org/apache/juneau/rest/RestParamDefaults.java  |  25 +--
 .../java/org/apache/juneau/rest/RestRequest.java   |  14 +-
 .../java/org/apache/juneau/rest/RestResponse.java  |  33 +--
 .../juneau/rest/annotation/RestResource.java       |   2 +-
 .../juneau/rest/reshandlers/DefaultHandler.java    |  62 ++++--
 .../rest/reshandlers/InputStreamHandler.java       |   6 +-
 .../juneau/rest/reshandlers/ReaderHandler.java     |   6 +-
 .../juneau/rest/reshandlers/RedirectHandler.java   |   6 +-
 .../juneau/rest/reshandlers/StreamableHandler.java |  10 +-
 .../juneau/rest/reshandlers/WritableHandler.java   |  10 +-
 .../reshandlers/ZipFileListResponseHandler.java    |   6 +-
 .../juneau/rest/response/MovedPermanently.java     |  13 +-
 .../juneau/rest/response/PermanentRedirect.java    |  13 +-
 .../org/apache/juneau/rest/response/SeeOther.java  |  13 +-
 .../juneau/rest/response/TemporaryRedirect.java    |  13 +-
 .../rest/annotation/ResponseAnnotationTest.java    |   4 +-
 .../org/apache/juneau/rest/response/BasicTest.java |  35 ++++
 28 files changed, 728 insertions(+), 237 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/ResponseMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/ResponseMeta.java
new file mode 100644
index 0000000..e447ae7
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/ResponseMeta.java
@@ -0,0 +1,231 @@
+// 
***************************************************************************************************************************
+// * 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.httppart;
+
+import static org.apache.juneau.internal.ClassFlags.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Represents the metadata gathered from a parameter or class annotated with 
{@link Response}.
+ */
+public class ResponseMeta {
+
+       /**
+        * Represents a non-existent meta object.
+        */
+       public static ResponseMeta NULL = new ResponseMeta(new 
Builder(PropertyStore.DEFAULT));
+
+       /**
+        * Create metadata from specified parameter.
+        *
+        * @param m The method containing the parameter or parameter type 
annotated with {@link Response}.
+        * @param i The parameter index.
+        * @param ps
+        *      Configuration information used to instantiate part serializers 
and part parsers.
+        *      <br>Can be <jk>null</jk>.
+        * @return Metadata about the parameter, or <jk>null</jk> if parameter 
or parameter type not annotated with {@link Response}.
+        */
+       public static ResponseMeta create(Method m, int i, PropertyStore ps) {
+               if (! hasAnnotation(Response.class, m, i))
+                       return null;
+               return new ResponseMeta.Builder(ps).apply(m, i).build();
+       }
+
+       /**
+        * Create metadata from specified method return.
+        *
+        * @param m The method annotated with {@link Response}.
+        * @param ps
+        *      Configuration information used to instantiate part serializers 
and part parsers.
+        *      <br>Can be <jk>null</jk>.
+        * @return Metadata about the parameter, or <jk>null</jk> if parameter 
or parameter type not annotated with {@link Response}.
+        */
+       public static ResponseMeta create(Method m, PropertyStore ps) {
+               if (! hasAnnotation(Response.class, m))
+                       return null;
+               return new ResponseMeta.Builder(ps).apply(m).build();
+       }
+
+       /**
+        * Create metadata from specified class.
+        *
+        * @param c The class annotated with {@link Response}.
+        * @param ps
+        *      Configuration information used to instantiate part serializers 
and part parsers.
+        *      <br>Can be <jk>null</jk>.
+        * @return Metadata about the class, or <jk>null</jk> if class not 
annotated with {@link Response}.
+        */
+       public static ResponseMeta create(Class<?> c, PropertyStore ps) {
+               if (! hasAnnotation(Response.class, c))
+                       return null;
+               return new ResponseMeta.Builder(ps).apply(c).build();
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       private final ClassMeta<?> cm;
+       private final int code;
+       private final Map<String,ResponsePropertyMeta> headers;
+       private final HttpPartSerializer partSerializer;
+       private final HttpPartSchema schema;
+       private final boolean usePartSerializer;
+
+       ResponseMeta(Builder b) {
+               this.cm = b.cm;
+               this.code = b.code;
+               this.partSerializer = 
ClassUtils.newInstance(HttpPartSerializer.class, b.partSerializer, true, b.ps);
+               this.schema = b.schema.build();
+               this.usePartSerializer = b.usePartSerializer || partSerializer 
!= null;
+               Map<String,ResponsePropertyMeta> headers = new 
LinkedHashMap<>();
+               for (Map.Entry<String,ResponsePropertyMeta.Builder> e : 
b.headers.entrySet()) {
+                       ResponsePropertyMeta pm = 
e.getValue().build(partSerializer);
+                       headers.put(e.getKey(), pm);
+
+               }
+               this.headers = Collections.unmodifiableMap(headers);
+       }
+
+       static class Builder {
+               ClassMeta<?> cm;
+               int code;
+               PropertyStore ps;
+               boolean usePartSerializer;
+               Class<? extends HttpPartSerializer> partSerializer;
+               HttpPartSchemaBuilder schema = HttpPartSchema.create();
+               Map<String,ResponsePropertyMeta.Builder> headers = new 
LinkedHashMap<>();
+
+               Builder(PropertyStore ps) {
+                       this.ps = ps;
+               }
+
+               Builder apply(Method m, int i) {
+                       return 
apply(m.getParameterTypes()[i]).apply(getAnnotation(Response.class, m, i));
+               }
+
+               Builder apply(Method m) {
+                       return 
apply(m.getReturnType()).apply(getAnnotation(Response.class, m));
+               }
+
+               Builder apply(Class<?> c) {
+                       apply(getAnnotation(Response.class, c));
+                       this.cm = BeanContext.DEFAULT.getClassMeta(c);
+                       for (Method m : ClassUtils.getAllMethods(c, false)) {
+                               if (isAll(m, PUBLIC, HAS_NO_ARGS)) {
+                                       Header h = 
m.getAnnotation(Header.class);
+                                       if (h != null) {
+                                               String n = h.name();
+                                               if (n.isEmpty())
+                                                       n = m.getName();
+                                               HttpPartSchemaBuilder s = 
HttpPartSchema.create().apply(h);
+                                               getProperty(n, 
HttpPartType.HEADER).apply(s).getter(m);
+                                       }
+                               }
+                       }
+                       return this;
+               }
+
+               Builder apply(Response rb) {
+                       if (rb != null) {
+                               if (rb.partSerializer() != 
HttpPartSerializer.Null.class)
+                                       partSerializer = rb.partSerializer();
+                               if (rb.usePartSerializer())
+                                       usePartSerializer = true;
+                               if (rb.value().length > 0)
+                                       code = rb.value()[0];
+                               if (rb.code().length > 0)
+                                       code = rb.code()[0];
+                               schema.apply(rb.schema());
+                       }
+                       return this;
+               }
+
+               ResponseMeta build() {
+                       return new ResponseMeta(this);
+               }
+
+               private ResponsePropertyMeta.Builder getProperty(String name, 
HttpPartType partType) {
+                       ResponsePropertyMeta.Builder b = headers.get(name);
+                       if (b == null) {
+                               b = 
ResponsePropertyMeta.create().name(name).partType(partType);
+                               headers.put(name, b);
+                       }
+                       return b;
+               }
+       }
+
+       /**
+        * Returns the HTTP status code.
+        *
+        * @return The HTTP status code.
+        */
+       public int getCode() {
+               return code;
+       }
+
+       /**
+        * Returns the schema information about the response object.
+        *
+        * @return The schema information about the response object.
+        */
+       public HttpPartSchema getSchema() {
+               return schema;
+       }
+
+       /**
+        * Returns metadata about headers defined on the response object.
+        *
+        * @return Metadata about headers defined on the response object.
+        */
+       public Collection<ResponsePropertyMeta> getHeaderMetas() {
+               return headers.values();
+       }
+
+       /**
+        * Returns the flag for whether the part serializer should be used to 
serialize this response.
+        *
+        * @return The flag for whether the part serializer should be used to 
serialize this response.
+        */
+       public boolean isUsePartSerializer() {
+               return usePartSerializer;
+       }
+
+       /**
+        * Returns the part serializer to use to serialize this response.
+        *
+        * @param _default The default serializer to use if it's not defined on 
this metadata.
+        * @return The part serializer to use to serialize this response.
+        */
+       public HttpPartSerializer getPartSerializer(HttpPartSerializer 
_default) {
+               return partSerializer == null ? _default : partSerializer;
+       }
+
+       /**
+        * Returns metadata about the class.
+        *
+        * @return Metadata about the class.
+        */
+       public ClassMeta<?> getClassMeta() {
+               return cm;
+       }
+
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/ResponsePropertyMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/ResponsePropertyMeta.java
new file mode 100644
index 0000000..53f149f
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/ResponsePropertyMeta.java
@@ -0,0 +1,136 @@
+// 
***************************************************************************************************************************
+// * 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.httppart;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Represents the metadata gathered from a getter method of a class annotated 
with {@link Response}.
+ */
+public class ResponsePropertyMeta {
+
+       static ResponsePropertyMeta.Builder create() {
+               return new Builder();
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       private final String partName;
+       private final Method getter;
+       private final HttpPartType partType;
+       private final HttpPartSerializer serializer;
+       private final HttpPartSchema schema;
+
+       ResponsePropertyMeta(Builder b, HttpPartSerializer serializer) {
+               this.partType = b.partType;
+               this.schema = b.schema.build();
+               this.partName = StringUtils.firstNonEmpty(schema.getName(), 
b.name);
+               this.getter = b.getter;
+               this.serializer = schema.getSerializer() == null ? serializer : 
ClassUtils.newInstance(HttpPartSerializer.class, schema.getSerializer(), true, 
b.ps);
+       }
+
+       static class Builder {
+               HttpPartType partType;
+               HttpPartSchemaBuilder schema;
+               String name;
+               Method getter;
+               PropertyStore ps = PropertyStore.DEFAULT;
+
+               Builder name(String value) {
+                       name = value;
+                       return this;
+               }
+
+               Builder getter(Method value) {
+                       getter = value;
+                       return this;
+               }
+
+               Builder partType(HttpPartType value) {
+                       partType = value;
+                       return this;
+               }
+
+               Builder schema(HttpPartSchemaBuilder value) {
+                       schema = value;
+                       return this;
+               }
+
+               Builder apply(HttpPartSchemaBuilder s) {
+                       schema = s;
+                       return this;
+               }
+
+               ResponsePropertyMeta build(HttpPartSerializer serializer) {
+                       return new ResponsePropertyMeta(this, serializer);
+               }
+       }
+
+       /**
+        * Returns the HTTP part name for this property (e.g. query parameter 
name).
+        *
+        * @return The HTTP part name, or <jk>null</jk> if it doesn't have a 
part name.
+        */
+       public String getPartName() {
+               return partName;
+       }
+
+       /**
+        * Returns the name of the Java method getter that defines this 
property.
+        *
+        * @return
+        *      The name of the Java method getter that defines this property.
+        *      <br>Never <jk>null</jk>.
+        */
+       public Method getGetter() {
+               return getter;
+       }
+
+       /**
+        * Returns the HTTP part type for this property (e.g. query parameter, 
header, etc...).
+        *
+        * @return
+        *      The HTTP part type for this property.
+        *      <br>Never <jk>null</jk>.
+        */
+       public HttpPartType getPartType() {
+               return partType;
+       }
+
+       /**
+        * Returns the serializer to use for serializing the bean property 
value.
+        *
+        * @param _default The default serializer to use if not defined on the 
annotation.
+        * @return The serializer to use for serializing the bean property 
value.
+        */
+       public HttpPartSerializer getSerializer(HttpPartSerializer _default) {
+               return serializer == null ? _default : serializer;
+       }
+
+       /**
+        * Returns the schema information gathered from annotations on the 
method and return type.
+        *
+        * @return
+        *      The schema information gathered from annotations on the method 
and return type.
+        *      <br>Never <jk>null</jk>.
+        */
+       public HttpPartSchema getSchema() {
+               return schema;
+       }
+}
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 4b1a0d5..c7d62de 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
@@ -23,7 +23,6 @@ import java.util.*;
 import javax.servlet.*;
 import javax.servlet.http.*;
 
-import org.apache.juneau.httppart.*;
 import org.apache.juneau.rest.exception.*;
 import org.apache.juneau.rest.helper.*;
 import org.apache.juneau.rest.util.*;
@@ -173,13 +172,11 @@ public class BasicRestCallHandler implements 
RestCallHandler {
                        }
 
                        if (res.hasOutput()) {
-                               Object output = res.getOutput();
+                               ResponseObject output = res.getOutput();
 
                                // Do any class-level transforming.
                                for (RestConverter converter : 
context.getConverters())
-                                       output = converter.convert(req, output);
-
-                               res.setOutput(output);
+                                       output.setValue(converter.convert(req, 
output.getValue()));
 
                                // Now serialize the output if there was any.
                                // Some subclasses may write to the 
OutputStream or Writer directly.
@@ -195,7 +192,7 @@ public class BasicRestCallHandler implements 
RestCallHandler {
                } catch (Throwable e) {
                        r1.setAttribute("Exception", e);
                        r1.setAttribute("ExecTime", System.currentTimeMillis() 
- startTime);
-                       handleError(r1, r2, req, e);
+                       handleError(r1, r2, e);
                }
 
                context.finishCall(r1, r2);
@@ -213,7 +210,7 @@ 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, Object)} method returns
+        * looking for the first one whose {@link 
ResponseHandler#handle(RestRequest, RestResponse, ResponseObject)} method 
returns
         * <jk>true</jk>.
         *
         * @param req The HTTP request.
@@ -223,7 +220,7 @@ public class BasicRestCallHandler implements 
RestCallHandler {
         * @throws RestException
         */
        @Override /* RestCallHandler */
-       public void handleResponse(RestRequest req, RestResponse res, Object 
output) throws IOException, RestException, NotImplemented {
+       public void handleResponse(RestRequest req, RestResponse res, 
ResponseObject output) throws IOException, RestException, NotImplemented {
                // Loop until we find the correct handler for the POJO.
                for (ResponseHandler h : context.getResponseHandlers())
                        if (h.handle(req, res, output))
@@ -269,13 +266,10 @@ public class BasicRestCallHandler implements 
RestCallHandler {
         * @throws IOException Can be thrown if a problem occurred trying to 
write to the output stream.
         */
        @Override /* RestCallHandler */
-       public synchronized void handleError(HttpServletRequest req, 
HttpServletResponse res, RestRequest rreq, Throwable e) throws IOException {
+       public synchronized void handleError(HttpServletRequest req, 
HttpServletResponse res, Throwable e) throws IOException {
 
-               RestMethodThrown rmt = rreq == null ? null : 
rreq.getRestMethodThrown(e);
                int occurrence = context == null ? 0 : 
context.getStackTraceOccurrence(e);
-               RestException e2 = (e instanceof RestException ? 
(RestException)e : new RestException(e, rmt == null ? 500 : 
rmt.getCode())).setOccurrence(occurrence);
-
-               HttpPartSerializer ps = rmt == null ? null : 
rmt.getPartSerializer();
+               RestException e2 = (e instanceof RestException ? 
(RestException)e : new RestException(e, 500)).setOccurrence(occurrence);
 
                Throwable t = e2.getRootCause();
                if (t != null) {
@@ -296,21 +290,13 @@ public class BasicRestCallHandler implements 
RestCallHandler {
                        }
 
                        try (PrintWriter w2 = w) {
-
-                               // Throwable can be handled as an HTTP part.
-                               if (rmt != null && ps != null) {
-                                       w2.append(ps.serialize(rmt.getSchema(), 
e));
-
-                               // It's some other exception.
-                               } else {
-                                       String httpMessage = 
RestUtils.getHttpResponseText(e2.getStatus());
-                                       if (httpMessage != null)
-                                               w2.append("HTTP 
").append(String.valueOf(e2.getStatus())).append(": 
").append(httpMessage).append("\n\n");
-                                       if (context != null && 
context.isRenderResponseStackTraces())
-                                               e.printStackTrace(w2);
-                                       else
-                                               
w2.append(e2.getFullStackMessage(true));
-                               }
+                               String httpMessage = 
RestUtils.getHttpResponseText(e2.getStatus());
+                               if (httpMessage != null)
+                                       w2.append("HTTP 
").append(String.valueOf(e2.getStatus())).append(": 
").append(httpMessage).append("\n\n");
+                               if (context != null && 
context.isRenderResponseStackTraces())
+                                       e.printStackTrace(w2);
+                               else
+                                       w2.append(e2.getFullStackMessage(true));
                        }
 
                } catch (Exception e1) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java
index 39cedbe..2e66ff4 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseHandler.java
@@ -112,5 +112,5 @@ public interface ResponseHandler {
         *      If some other exception occurred.
         *      Can be used to provide an appropriate HTTP response code and 
message.
         */
-       boolean handle(RestRequest req, RestResponse res, Object output) throws 
IOException, RestException;
+       boolean handle(RestRequest req, RestResponse res, ResponseObject 
output) throws IOException, RestException;
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseObject.java
similarity index 51%
copy from 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
copy to 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseObject.java
index 780f441..4b905e2 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseObject.java
@@ -10,52 +10,78 @@
 // * "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.response;
+package org.apache.juneau.rest;
 
-import java.net.*;
-
-import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
 
 /**
- * Represents an <code>HTTP 301 Moved Permanently</code> response.
- *
- * <p>
- * This and all future requests should be directed to the given URI.
+ * A simple pairing of a response object and metadata on how to serialize that 
response object.
  */
-@Response(code=301, example="'Moved Permanently'")
-public class MovedPermanently {
-
-       /** Reusable instance. */
-       public static final MovedPermanently INSTANCE = new MovedPermanently();
+public class ResponseObject {
 
-       private final URI location;
+       private ResponseMeta meta;
+       private Object value;
 
        /**
         * Constructor.
+        *
+        * @param meta Metadata about the specified value.
+        * @param value The POJO that makes up the response.
         */
-       public MovedPermanently() {
-               this(null);
+       public ResponseObject(ResponseMeta meta, Object value) {
+               this.meta = meta;
+               this.value = value;
        }
 
        /**
-        * Constructor.
+        * Returns the metadata about this response.
+        *
+        * @return
+        *      The metadata about this response.
+        *      <jk>Never <jk>null</jk>.
+        */
+       public ResponseMeta getMeta() {
+               return meta;
+       }
+
+       /**
+        * Returns the POJO that makes up this response.
         *
-        * @param location <code>Location</code> header value.
+        * @return
+        *      The POJO that makes up this response.
+        *      <jk>Never <jk>null</jk>.
         */
-       public MovedPermanently(URI location) {
-               this.location = location;
+       public Object getValue() {
+               return value;
        }
 
-       @Override /* Object */
-       public String toString() {
-               return "Moved Permanently";
+       /**
+        * Returns <jk>true</jk> if this response object is of the specified 
type.
+        *
+        * @param c The type to check against.
+        * @return <jk>true</jk> if this response object is of the specified 
type.
+        */
+       public boolean isType(Class<?> c) {
+               return c.isInstance(value);
        }
 
        /**
-        * @return <code>Location</code> header value.
+        * Returns this value cast to the specified class.
+        *
+        * @param c The class to cast to.
+        * @return This value cast to the specified class.
+        */
+       @SuppressWarnings("unchecked")
+       public <T> T getValue(Class<T> c) {
+               return (T)value;
+       }
+
+       /**
+        * Sets the POJO value for this response.
+        *
+        * @param value The POJO value to set.
         */
-       @Header(name="Location", description="")
-       public URI getLocation() {
-               return location;
+       public void setValue(Object value) {
+               this.value = value;
        }
-}
\ No newline at end of file
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponsePart.java
similarity index 51%
copy from 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
copy to 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponsePart.java
index c94c23b..31b5b89 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponsePart.java
@@ -10,53 +10,58 @@
 // * "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.response;
+package org.apache.juneau.rest;
 
-import java.net.*;
-
-import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.serializer.*;
 
 /**
- * Represents an <code>HTTP 308 Permanent Redirect</code> response.
- *
- * <p>
- * The request and all future requests should be repeated using another URI. 
307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP 
method to change.
- * So, for example, submitting a form to a permanently redirected resource may 
continue smoothly.
+ * Represents part of an HTTP response such as an HTTP response header.
  */
-@Response(code=308, example="'Permanent Redirect'")
-public class PermanentRedirect {
-
-       /** Reusable instance. */
-       public static final PermanentRedirect INSTANCE = new 
PermanentRedirect();
-
-       private final URI location;
+public class ResponsePart {
+       private final String name;
+       private final Object part;
+       private final HttpPartType partType;
+       private final HttpPartSchema schema;
+       private final HttpPartSerializer serializer;
+       private final SerializerSessionArgs args;
 
        /**
         * Constructor.
+        *
+        * @param name The HTTP part name (e.g. the response header name).
+        * @param partType The HTTP part type.
+        * @param schema Schema information about the part.
+        * @param serializer The part serializer to use to serialize the part.
+        * @param part The part POJO being serialized.
+        * @param args Session arguments to pass to the serializer.
         */
-       public PermanentRedirect() {
-               this(null);
+       public ResponsePart(String name, HttpPartType partType, HttpPartSchema 
schema, HttpPartSerializer serializer, Object part, SerializerSessionArgs args) 
{
+               this.name = name;
+               this.partType = partType;
+               this.schema = schema;
+               this.serializer = serializer;
+               this.part = part;
+               this.args = args;
        }
 
        /**
-        * Constructor.
+        * Returns the name of the part.
         *
-        * @param location <code>Location</code> header value.
+        * @return The name of the part.
         */
-       public PermanentRedirect(URI location) {
-               this.location = location;
-       }
-
-       @Override /* Object */
-       public String toString() {
-               return "Permanent Redirect";
+       public String getName() {
+               return name;
        }
 
        /**
-        * @return <code>Location</code> header value.
+        * Returns the value of the part converted to a string.
+        *
+        * @return The value of the part converted to a string.
+        * @throws SchemaValidationException
+        * @throws SerializeException
         */
-       @Header(name="Location", description="")
-       public URI getLocation() {
-               return location;
+       public String getValue() throws SchemaValidationException, 
SerializeException {
+               return serializer.createSession(args).serialize(partType, 
schema, part);
        }
-}
\ No newline at end of file
+}
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 41f5f18..2eb3f10 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
@@ -78,7 +78,7 @@ public interface RestCallHandler {
         * @throws IOException
         * @throws RestException
         */
-       public void handleResponse(RestRequest req, RestResponse res, Object 
output) throws IOException, RestException ;
+       public void handleResponse(RestRequest req, RestResponse res, 
ResponseObject output) throws IOException, RestException ;
 
        /**
         * Handle the case where a matching method was not found.
@@ -95,13 +95,10 @@ public interface RestCallHandler {
         *
         * @param req The servlet request.
         * @param res The servlet response.
-        * @param rreq
-        *      The REST request.
-        *      <br>This may be <jk>null</jk> if an error occurred before this 
was initialized.
         * @param e The exception that occurred.
         * @throws IOException Can be thrown if a problem occurred trying to 
write to the output stream.
         */
-       public void handleError(HttpServletRequest req, HttpServletResponse 
res, RestRequest rreq, Throwable e) throws IOException;
+       public void handleError(HttpServletRequest req, HttpServletResponse 
res, Throwable e) throws IOException;
 
        /**
         * Returns the session objects for the specified request.
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 5cb30c2..8e3307c 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
@@ -834,7 +834,7 @@ public final class RestContext extends BeanContext {
         * <p>
         * Enables the following:
         * <ul>
-        *      <li>A message and stack trace is printed to STDERR when {@link 
BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, 
RestRequest, Throwable)} is called.
+        *      <li>A message and stack trace is printed to STDERR when {@link 
BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, 
Throwable)} is called.
         * </ul>
         */
        public static final String REST_debug = PREFIX + "debug.b";
@@ -2030,7 +2030,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, 
RestRequest, Throwable)}.
+        *              That method is used by {@link 
BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, 
Throwable)}.
         * </ul>
         */
        public static final String REST_renderResponseStackTraces = PREFIX + 
"renderResponseStackTraces.b";
@@ -2666,7 +2666,7 @@ public final class RestContext extends BeanContext {
         * Affects the following methods:
         * <ul>
         *      <li class='jm'>{@link 
RestContext#getStackTraceOccurrence(Throwable) 
RestContext.getStackTraceOccurrance(Throwable)}
-        *      <li class='jm'>{@link 
RestCallHandler#handleError(HttpServletRequest, HttpServletResponse, 
RestRequest, Throwable)}
+        *      <li class='jm'>{@link 
RestCallHandler#handleError(HttpServletRequest, HttpServletResponse, Throwable)}
         *      <li class='jm'>{@link RestException#getOccurrence()} - Returns 
the number of times this exception occurred.
         * </ul>
         *
@@ -2853,6 +2853,8 @@ public final class RestContext extends BeanContext {
        private final ClasspathResourceManager staticResourceManager;
        private final ConcurrentHashMap<Integer,AtomicInteger> stackTraceHashes 
= new ConcurrentHashMap<>();
 
+       private final Map<Class<?>,ResponseMeta> responseMetas = new 
ConcurrentHashMap<>();
+
        /**
         * Constructor.
         *
@@ -3050,7 +3052,7 @@ public final class RestContext extends BeanContext {
                                                                        if (rc 
!= SC_OK)
                                                                                
return rc;
 
-                                                                       final 
Object o = res.getOutput();
+                                                                       final 
ResponseObject ro = res.getOutput();
 
                                                                        if 
("GET".equals(req.getMethod())) {
                                                                                
res.setOutput(getMethodInfo(remoteableMethods.values()));
@@ -3073,7 +3075,7 @@ public final class RestContext extends BeanContext {
                                                                                
                                args = p.parseArgs(in, 
m.getGenericParameterTypes());
                                                                                
                        }
                                                                                
                }
-                                                                               
                Object output = m.invoke(o, args);
+                                                                               
                Object output = m.invoke(ro.getValue(), args);
                                                                                
                res.setOutput(output);
                                                                                
                return SC_OK;
                                                                                
        } catch (Exception e) {
@@ -4355,7 +4357,7 @@ public final class RestContext extends BeanContext {
 
                        } else if (hasAnnotation(Response.class, method, i)) {
                                s = HttpPartSchema.create(Response.class, 
method, i);
-                               rp[i] = new 
RestParamDefaults.ResponseObject(method, s, t, ps);
+                               rp[i] = new 
RestParamDefaults.ResponseParamObject(method, i, s, t, ps);
                        } else if (hasAnnotation(ResponseHeader.class, method, 
i)) {
                                s = HttpPartSchema.create(ResponseHeader.class, 
method, i);
                                rp[i] = new 
RestParamDefaults.ResponseHeaderObject(method, s, t, ps);
@@ -4547,4 +4549,21 @@ public final class RestContext extends BeanContext {
        public BeanSessionArgs createDefaultSessionArgs() {
                throw new NoSuchMethodError();
        }
+
+       ResponseMeta getResponseMetaForObject(Object o) {
+               if (o == null)
+                       return null;
+               return getResponseMeta(o.getClass());
+       }
+
+       ResponseMeta getResponseMeta(Class<?> c) {
+               ResponseMeta rm = responseMetas.get(c);
+               if (rm == ResponseMeta.NULL)
+                       return null;
+               if (rm != null)
+                       return rm;
+               rm = ResponseMeta.create(c, getPropertyStore());
+               responseMetas.put(c, rm == null ? ResponseMeta.NULL : rm);
+               return rm;
+       }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
index 619d14b..12eec28 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
@@ -519,18 +519,40 @@ public class RestJavaMethod implements 
Comparable<RestJavaMethod>  {
                                if (! guard.guard(req, res))
                                        return SC_OK;
 
-                       Object output = method.invoke(context.getResource(), 
args);
-                       if (! method.getReturnType().equals(Void.TYPE))
-                               if (output != null || ! 
res.getOutputStreamCalled())
-                                       res.setOutput(output);
+                       Object output;
+                       try {
+                               output = method.invoke(context.getResource(), 
args);
+                               if (res.getStatus() == 0)
+                                       res.setStatus(200);
+                               RestMethodReturn rmr = 
req.getRestMethodReturn();
+                               if (! method.getReturnType().equals(Void.TYPE)) 
{
+                                       if (output != null || ! 
res.getOutputStreamCalled()) {
+                                               ResponseMeta rm = 
rmr.getResponseMeta();
+                                               if (rm == null)
+                                                       rm = 
context.getResponseMetaForObject(output);
+                                               res.setOutput(new 
ResponseObject(rm, output));
+                                       }
+                               }
+                       } catch (InvocationTargetException e) {
+                               Throwable e2 = e.getTargetException();          
// Get the throwable thrown from the doX() method.
+                               if (res.getStatus() == 0)
+                                       res.setStatus(500);
+                               RestMethodThrown rmt = 
req.getRestMethodThrown(e2);
+                               ResponseMeta rm = rmt == null ? null : 
rmt.getResponseMeta();
+                               if (rm == null)
+                                       rm = 
context.getResponseMetaForObject(e2);
+                               if (rm != null)
+                                       res.setOutput(new ResponseObject(rm, 
e2));
+                               else
+                                       throw e;
+                       }
 
                        context.postCall(req, res);
 
                        if (res.hasOutput()) {
-                               output = res.getOutput();
+                               ResponseObject ro = res.getOutput();
                                for (RestConverter converter : converters)
-                                       output = converter.convert(req, output);
-                               res.setOutput(output);
+                                       ro.setValue(converter.convert(req, 
ro.getValue()));
                        }
                } catch (IllegalArgumentException e) {
                        throw new BadRequest(e,
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodReturn.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodReturn.java
index c8f75ee..29d1876 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodReturn.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodReturn.java
@@ -19,7 +19,6 @@ import java.lang.reflect.*;
 import org.apache.juneau.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
-import org.apache.juneau.internal.*;
 
 /**
  * Contains metadata about the return type on a REST Java method.
@@ -29,21 +28,18 @@ public class RestMethodReturn {
        private final Type type;
        private final int code;
        private final ObjectMap api;
-       private final HttpPartSchema schema;
-       private final HttpPartSerializer partSerializer;
+       private final ResponseMeta responseMeta;
 
        RestMethodReturn(Method m, HttpPartSerializer partSerializer, 
PropertyStore ps) {
+               this.responseMeta = ResponseMeta.create(m, ps);
+
                HttpPartSchema s = HttpPartSchema.DEFAULT;
                if (hasAnnotation(Response.class, m))
                        s = HttpPartSchema.create(Response.class, m);
 
-               this.schema = s;
                this.type = m.getGenericReturnType();
                this.api = HttpPartSchema.getApiCodeMap(s, 200).unmodifiable();
                this.code = s.getCode(200);
-
-               boolean usePS = (s.isUsePartSerializer() || s.getSerializer() 
!= null);
-               this.partSerializer = usePS ? 
ObjectUtils.firstNonNull(ClassUtils.newInstance(HttpPartSerializer.class, 
s.getSerializer(), true, ps), partSerializer) : null;
        }
 
        /**
@@ -74,22 +70,13 @@ public class RestMethodReturn {
        }
 
        /**
-        * Returns the schema for the method return type.
-        *
-        * @return The schema for the method return type.  Never <jk>null</jk>.
-        */
-       public HttpPartSchema getSchema() {
-               return schema;
-       }
-
-       /**
-        * Returns the part serializer for the method return type.
+        * Returns metadata about the response object.
         *
         * @return
-        *      The part serializer for the method return type.
-        *      <br><jk>null</jk> if {@link Response#usePartSerializer()} is 
<jk>false</jk>.
+        *      Metadata about the response object.
+        *      <br>Can be <jk>null</jk> if no {@link Response} annotation is 
associated with the return.
         */
-       public HttpPartSerializer getPartSerializer() {
-               return partSerializer;
+       public ResponseMeta getResponseMeta() {
+               return responseMeta;
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodThrown.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodThrown.java
index a4176f5..ada4e7f 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodThrown.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodThrown.java
@@ -17,7 +17,6 @@ import static org.apache.juneau.internal.ReflectionUtils.*;
 import org.apache.juneau.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
-import org.apache.juneau.internal.*;
 
 /**
  * Contains metadata about a throwable on a REST Java method.
@@ -27,21 +26,18 @@ public class RestMethodThrown {
        final Class<?> type;
        final int code;
        final ObjectMap api;
-       private final HttpPartSchema schema;
-       final HttpPartSerializer partSerializer;
+       private final ResponseMeta responseMeta;
 
        RestMethodThrown(Class<?> type, HttpPartSerializer partSerializer, 
PropertyStore ps) {
+               this.responseMeta = ResponseMeta.create(type, ps);
+
                HttpPartSchema s = HttpPartSchema.DEFAULT;
                if (hasAnnotation(Response.class, type))
                        s = HttpPartSchema.create(Response.class, type);
 
-               this.schema = s;
                this.type = type;
                this.api = HttpPartSchema.getApiCodeMap(s, 500).unmodifiable();
                this.code = s.getCode(500);
-
-               boolean usePS = (s.isUsePartSerializer() || s.getSerializer() 
!= null);
-               this.partSerializer = usePS ? 
ObjectUtils.firstNonNull(ClassUtils.newInstance(HttpPartSerializer.class, 
s.getSerializer(), true, ps), partSerializer) : null;
        }
 
        /**
@@ -72,22 +68,13 @@ public class RestMethodThrown {
        }
 
        /**
-        * Returns the schema for the method return type.
-        *
-        * @return The schema for the method return type.  Never <jk>null</jk>.
-        */
-       public HttpPartSchema getSchema() {
-               return schema;
-       }
-
-       /**
-        * Returns the part serializer for the method return type.
+        * Returns metadata about the thrown object.
         *
         * @return
-        *      The part serializer for the method return type.
-        *      <br><jk>null</jk> if {@link Response#usePartSerializer()} is 
<jk>false</jk>.
+        *      Metadata about the thrown object.
+        *      <br>Can be <jk>null</jk> if no {@link Response} annotation is 
associated with the thrown object.
         */
-       public HttpPartSerializer getPartSerializer() {
-               return partSerializer;
+       public ResponseMeta getResponseMeta() {
+               return responseMeta;
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
index a45b31c..80328f0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
@@ -642,7 +642,7 @@ class RestParamDefaults {
                                @Override
                                public void onSet(Object newValue) {
                                        try {
-                                               res.setHeader(name, 
partSerializer.createSession(req.getSerializerSessionArgs()).serialize(HttpPartType.HEADER,
 schema, newValue));
+                                               res.setHeader(new 
ResponsePart(name, HttpPartType.HEADER, schema, partSerializer, newValue, 
req.getSerializerSessionArgs()));
                                        } catch (SerializeException | 
SchemaValidationException e) {
                                                throw new RuntimeException(e);
                                        }
@@ -660,17 +660,13 @@ class RestParamDefaults {
                }
        }
 
-       static final class ResponseObject extends RestMethodParam {
-               final HttpPartSerializer partSerializer;
-               final boolean usePartSerializer;
-               final HttpPartSchema schema;
+       static final class ResponseParamObject extends RestMethodParam {
                private String _default;
+               final ResponseMeta meta;
 
-               protected ResponseObject(Method m, HttpPartSchema s, Type t, 
PropertyStore ps) {
+               protected ResponseParamObject(Method m, int i, HttpPartSchema 
s, Type t, PropertyStore ps) {
                        super(RESPONSE, m, s.getName(), t, 
HttpPartSchema.getApiCodeMap(s, 200));
-                       this.usePartSerializer = s.isUsePartSerializer() || 
s.getSerializer() != null;
-                       this.partSerializer = usePartSerializer ? 
ClassUtils.newInstance(HttpPartSerializer.class, s.getSerializer(), true, ps) : 
null;
-                       this.schema = s;
+                       this.meta = ResponseMeta.create(m, i, ps);
                        this._default = s.getDefault();
 
                        if (getTypeClass() == null)
@@ -686,16 +682,7 @@ class RestParamDefaults {
                        v.listener(new ValueListener() {
                                @Override
                                public void onSet(Object newValue) {
-                                       try {
-                                               if (usePartSerializer) {
-                                                       HttpPartSerializer ps = 
partSerializer == null ? req.getPartSerializer() : partSerializer;
-                                                       if (ps != null)
-                                                               newValue = new 
StringReader(ps.serialize(HttpPartType.BODY, schema, newValue));
-                                               }
-                                               res.setOutput(newValue);
-                                       } catch (SchemaValidationException | 
SerializeException e) {
-                                               throw new RuntimeException(e);
-                                       }
+                                       res.setOutput(new ResponseObject(meta, 
newValue));
                                }
                        });
                        if (_default != null) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 5507f15..9992ee7 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
@@ -1528,13 +1528,23 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
                return sb.toString();
        }
 
-       SerializerSessionArgs getSerializerSessionArgs() {
+       /**
+        * Returns the session arguments to pass to serializers.
+        *
+        * @return The session arguments to pass to serializers.
+        */
+       public SerializerSessionArgs getSerializerSessionArgs() {
                if (serializerSessionArgs == null)
                        serializerSessionArgs = new 
SerializerSessionArgs(getProperties(), getJavaMethod(), getLocale(), 
getHeaders().getTimeZone(), null, isDebug() ? true : null, getUriContext(), 
isPlainText() ? true : null);
                return serializerSessionArgs;
        }
 
-       ParserSessionArgs getParserSessionArgs() {
+       /**
+        * Returns the session arguments to pass to parsers.
+        *
+        * @return The session arguments to pass to parsers.
+        */
+       public ParserSessionArgs getParserSessionArgs() {
                if (parserSessionArgs == null)
                        parserSessionArgs = new 
ParserSessionArgs(getProperties(), getJavaMethod(), getLocale(), 
getHeaders().getTimeZone(), null, isDebug() ? true : null, getUriContext());
                return parserSessionArgs;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index 269e8b0..8d9d24b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -58,7 +58,7 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
 
        private final RestRequest request;
        private RestJavaMethod restJavaMethod;
-       private Object output;                       // The POJO being sent to 
the output.
+       private ResponseObject output;                       // The POJO being 
sent to the output.
        private boolean isNullOutput;                // The output is null (as 
opposed to not being set at all)
        private RequestProperties properties;                // Response 
properties
        private ServletOutputStream sos;
@@ -189,11 +189,17 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
         * @return This object (for method chaining).
         */
        public RestResponse setOutput(Object output) {
-               this.output = output;
+               this.output = new ResponseObject(null, output);
                this.isNullOutput = output == null;
                return this;
        }
 
+       RestResponse setOutput(ResponseObject output) {
+               this.output = output;
+               this.isNullOutput = output.getValue() == null;
+               return this;
+       }
+
        /**
         * Returns a programmatic interface for setting properties for the HTML 
doc view.
         *
@@ -309,7 +315,7 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
         * @return This object (for method chaining).
         */
        public RestResponse setOutputs(Object...output) {
-               this.output = output;
+               this.output = new ResponseObject(null, output);
                return this;
        }
 
@@ -318,7 +324,7 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
         *
         * @return The output object.
         */
-       public Object getOutput() {
+       public ResponseObject getOutput() {
                return output;
        }
 
@@ -489,15 +495,6 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
                super.sendRedirect(uri);
        }
 
-       /**
-        * Returns the HTTP-part serializer associated with this response.
-        *
-        * @return The HTTP-part serializer associated with this response.
-        */
-       public HttpPartSerializer getPartSerializer() {
-               return restJavaMethod.partSerializer;
-       }
-
        @Override /* ServletResponse */
        public void setHeader(String name, String value) {
                // Jetty doesn't set the content type correctly if set through 
this method.
@@ -508,6 +505,16 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
                        super.setHeader(name, value);
        }
 
+       /**
+        * Same as {@link #setHeader(String, String)} but header is defined as 
a response part
+        *
+        * @param h Header to set.
+        * @throws SchemaValidationException
+        * @throws SerializeException
+        */
+       public void setHeader(ResponsePart h) throws SchemaValidationException, 
SerializeException {
+               setHeader(h.getName(), h.getValue());
+       }
 
        @Override /* ServletResponse */
        public void flushBuffer() throws IOException {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
index daf3fea..881738f 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestResource.java
@@ -952,7 +952,7 @@ public @interface RestResource {
         * <p>
         * Enables the following:
         * <ul>
-        *      <li>A message and stack trace is printed to STDERR when {@link 
BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, 
RestRequest, Throwable)} is called.
+        *      <li>A message and stack trace is printed to STDERR when {@link 
BasicRestCallHandler#handleError(HttpServletRequest, HttpServletResponse, 
Throwable)} is called.
         * </ul>
         *
         * <h5 class='section'>Notes:</h5>
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
index 9821f3d..425b60a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.rest.reshandlers;
 
 import java.io.*;
+import java.net.*;
 import java.util.*;
 
 import org.apache.juneau.*;
@@ -46,13 +47,45 @@ public class DefaultHandler implements ResponseHandler {
 
        @SuppressWarnings("resource")
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, InternalServerError, NotAcceptable {
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, InternalServerError, NotAcceptable {
                SerializerGroup g = res.getSerializers();
                String accept = req.getHeaders().getString("Accept", "");
                SerializerMatch sm = g.getSerializerMatch(accept);
 
-               RestMethodReturn rmr = req.getRestMethodReturn();
-               res.setStatus(rmr.getCode());
+               Object o = ro.getValue();
+
+               ResponseMeta rm = ro.getMeta();
+
+               if (rm != null) {
+                       if (rm.getCode() != 0)
+                               res.setStatus(rm.getCode());
+
+                       for (ResponsePropertyMeta hm : rm.getHeaderMetas()) {
+                               try {
+                                       Object headerValue = 
hm.getGetter().invoke(o);
+                                       if (headerValue instanceof URI)
+                                               headerValue = 
req.getUriResolver().resolve(headerValue);
+                                       res.setHeader(new 
ResponsePart(hm.getPartName(), HttpPartType.HEADER, hm.getSchema(), 
hm.getSerializer(req.getPartSerializer()), headerValue, 
req.getSerializerSessionArgs()));
+                               } catch (Exception e) {
+                                       throw new InternalServerError(e, "Could 
not set header ''{0}''", hm.getPartName());
+                               }
+                       }
+
+                       if (rm.isUsePartSerializer()) {
+                               res.setContentType("text/plain");
+                               HttpPartSerializer ps = 
rm.getPartSerializer(req.getPartSerializer());
+                               if (ps != null) {
+                                       try (FinishablePrintWriter w = 
res.getNegotiatedWriter()) {
+                                               
w.append(ps.serialize(HttpPartType.BODY, rm.getSchema(), o));
+                                               w.flush();
+                                               w.finish();
+                                       } catch (SchemaValidationException | 
SerializeException e) {
+                                               throw new 
InternalServerError(e);
+                                       }
+                                       return true;
+                               }
+                       }
+               }
 
                if (sm != null) {
                        Serializer s = sm.getSerializer();
@@ -82,19 +115,19 @@ public class DefaultHandler implements ResponseHandler {
                                        if (req.isPlainText()) {
                                                FinishablePrintWriter w = 
res.getNegotiatedWriter();
                                                ByteArrayOutputStream baos = 
new ByteArrayOutputStream();
-                                               session.serialize(output, baos);
+                                               session.serialize(o, baos);
                                                
w.write(StringUtils.toSpacedHex(baos.toByteArray()));
                                                w.flush();
                                                w.finish();
                                        } else {
                                                FinishableServletOutputStream 
os = res.getNegotiatedOutputStream();
-                                               session.serialize(output, os);
+                                               session.serialize(o, os);
                                                os.flush();
                                                os.finish();
                                        }
                                } else {
                                        FinishablePrintWriter w = 
res.getNegotiatedWriter();
-                                       session.serialize(output, w);
+                                       session.serialize(o, w);
                                        w.flush();
                                        w.finish();
                                }
@@ -105,25 +138,12 @@ public class DefaultHandler implements ResponseHandler {
 
                }
 
-               HttpPartSerializer ps = rmr.getPartSerializer();
-               if (ps != null) {
-                       try {
-                               FinishablePrintWriter w = 
res.getNegotiatedWriter();
-                               w.append(ps.serialize(rmr.getSchema(), output));
-                               w.flush();
-                               w.finish();
-                       } catch (SchemaValidationException | SerializeException 
e) {
-                               throw new InternalServerError(e);
-                       }
-                       return true;
-               }
-
                // Non-existent Accept or plain/text can just be serialized 
as-is.
                if ("".equals(accept) || "plain/text".equals(accept)) {
                        FinishablePrintWriter w = res.getNegotiatedWriter();
-                       ClassMeta<?> cm = 
req.getBeanSession().getClassMetaForObject(output);
+                       ClassMeta<?> cm = 
req.getBeanSession().getClassMetaForObject(o);
                        if (cm != null)
-                               w.append(cm.toString(output));
+                               w.append(cm.toString(o));
                        w.flush();
                        w.finish();
                        return true;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/InputStreamHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/InputStreamHandler.java
index 4fe68be..8f2948e 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/InputStreamHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/InputStreamHandler.java
@@ -35,11 +35,11 @@ import org.apache.juneau.utils.*;
 public final class InputStreamHandler implements ResponseHandler {
 
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, NotAcceptable, RestException {
-               if (output instanceof InputStream) {
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, NotAcceptable, RestException {
+               if (ro.isType(InputStream.class)) {
                        res.setHeader("Content-Type", res.getContentType());
                        try (OutputStream os = res.getNegotiatedOutputStream()) 
{
-                               IOPipe.create(output, os).run();
+                               IOPipe.create(ro.getValue(InputStream.class), 
os).run();
                        }
                        return true;
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ReaderHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ReaderHandler.java
index 027f6fa..c89c8c1 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ReaderHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ReaderHandler.java
@@ -32,10 +32,10 @@ import org.apache.juneau.utils.*;
 public final class ReaderHandler implements ResponseHandler {
 
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, NotAcceptable, RestException {
-               if (output instanceof Reader) {
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, NotAcceptable, RestException {
+               if (ro.isType(Reader.class)) {
                        try (Writer w = res.getNegotiatedWriter()) {
-                               IOPipe.create(output, w).run();
+                               IOPipe.create(ro.getValue(Reader.class), 
w).run();
                        }
                        return true;
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/RedirectHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/RedirectHandler.java
index 354b4a3..798b9be 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/RedirectHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/RedirectHandler.java
@@ -28,9 +28,9 @@ import org.apache.juneau.rest.helper.*;
 public final class RedirectHandler implements ResponseHandler {
 
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, RestException {
-               if (output instanceof Redirect) {
-                       Redirect r = (Redirect)output;
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, RestException {
+               if (ro.isType(Redirect.class)) {
+                       Redirect r = ro.getValue(Redirect.class);
                        String uri = req.getUriResolver().resolve(r.getURI());
                        int rc = r.getHttpResponseCode();
                        if (rc != 0)
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/StreamableHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/StreamableHandler.java
index d8d1486..5752ada 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/StreamableHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/StreamableHandler.java
@@ -37,10 +37,10 @@ import org.apache.juneau.rest.helper.*;
 public final class StreamableHandler implements ResponseHandler {
 
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, RestException {
-               if (output instanceof Streamable) {
-                       if (output instanceof StreamResource) {
-                               StreamResource r = (StreamResource)output;
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, RestException {
+               if (ro.isType(Streamable.class)) {
+                       if (ro.isType(StreamResource.class)) {
+                               StreamResource r = 
ro.getValue(StreamResource.class);
                                MediaType mediaType = r.getMediaType();
                                if (mediaType != null)
                                        
res.setContentType(mediaType.toString());
@@ -48,7 +48,7 @@ public final class StreamableHandler implements 
ResponseHandler {
                                        res.setHeader(h.getKey(), 
asString(h.getValue()));
                        }
                        try (OutputStream os = res.getOutputStream()) {
-                               ((Streamable)output).streamTo(os);
+                               ro.getValue(Streamable.class).streamTo(os);
                        }
                        return true;
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/WritableHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/WritableHandler.java
index 61299e6..ddcf849 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/WritableHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/WritableHandler.java
@@ -37,10 +37,10 @@ import org.apache.juneau.rest.helper.*;
 public final class WritableHandler implements ResponseHandler {
 
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, NotAcceptable, RestException {
-               if (output instanceof Writable) {
-                       if (output instanceof ReaderResource) {
-                               ReaderResource r = (ReaderResource)output;
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, NotAcceptable, RestException {
+               if (ro.isType(Writable.class)) {
+                       if (ro.isType(ReaderResource.class)) {
+                               ReaderResource r = 
ro.getValue(ReaderResource.class);
                                MediaType mediaType = r.getMediaType();
                                if (mediaType != null)
                                        
res.setContentType(mediaType.toString());
@@ -48,7 +48,7 @@ public final class WritableHandler implements ResponseHandler 
{
                                        res.setHeader(h.getKey(), 
asString(h.getValue()));
                        }
                        try (Writer w = res.getNegotiatedWriter()) {
-                               ((Writable)output).writeTo(w);
+                               ro.getValue(Writable.class).writeTo(w);
                        }
                        return true;
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ZipFileListResponseHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ZipFileListResponseHandler.java
index c548d05..5deda07 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ZipFileListResponseHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/ZipFileListResponseHandler.java
@@ -44,9 +44,9 @@ import org.apache.juneau.utils.ZipFileList.*;
 public class ZipFileListResponseHandler implements ResponseHandler {
 
        @Override /* ResponseHandler */
-       public boolean handle(RestRequest req, RestResponse res, Object output) 
throws IOException, RestException {
-               if (output.getClass() == ZipFileList.class) {
-                       ZipFileList m = (ZipFileList)output;
+       public boolean handle(RestRequest req, RestResponse res, ResponseObject 
ro) throws IOException, RestException {
+               if (ro.isType(ZipFileList.class)) {
+                       ZipFileList m = ro.getValue(ZipFileList.class);
                        res.setContentType("application/zip");
                        res.setHeader("Content-Disposition", 
"attachment;filename=" + m.fileName); //$NON-NLS-2$
                        try (OutputStream os = res.getOutputStream()) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
index 780f441..06d4e50 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/MovedPermanently.java
@@ -34,7 +34,7 @@ public class MovedPermanently {
         * Constructor.
         */
        public MovedPermanently() {
-               this(null);
+               this((URI)null);
        }
 
        /**
@@ -46,6 +46,15 @@ public class MovedPermanently {
                this.location = location;
        }
 
+       /**
+        * Constructor.
+        *
+        * @param location <code>Location</code> header value.
+        */
+       public MovedPermanently(String location) {
+               this.location = URI.create(location);
+       }
+
        @Override /* Object */
        public String toString() {
                return "Moved Permanently";
@@ -54,7 +63,7 @@ public class MovedPermanently {
        /**
         * @return <code>Location</code> header value.
         */
-       @Header(name="Location", description="")
+       @Header(name="Location")
        public URI getLocation() {
                return location;
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
index c94c23b..a4fb513 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/PermanentRedirect.java
@@ -35,7 +35,16 @@ public class PermanentRedirect {
         * Constructor.
         */
        public PermanentRedirect() {
-               this(null);
+               this((URI)null);
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param location <code>Location</code> header value.
+        */
+       public PermanentRedirect(String location) {
+               this.location = URI.create(location);
        }
 
        /**
@@ -55,7 +64,7 @@ public class PermanentRedirect {
        /**
         * @return <code>Location</code> header value.
         */
-       @Header(name="Location", description="")
+       @Header(name="Location")
        public URI getLocation() {
                return location;
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
index b2f9056..8b7848b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/SeeOther.java
@@ -35,7 +35,16 @@ public class SeeOther {
         * Constructor.
         */
        public SeeOther() {
-               this(null);
+               this((URI)null);
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param location <code>Location</code> header value.
+        */
+       public SeeOther(String location) {
+               this.location = URI.create(location);
        }
 
        /**
@@ -55,7 +64,7 @@ public class SeeOther {
        /**
         * @return <code>Location</code> header value.
         */
-       @Header(name="Location", description="")
+       @Header(name="Location")
        public URI getLocation() {
                return location;
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
index f24197f..a46121c 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/response/TemporaryRedirect.java
@@ -36,7 +36,16 @@ public class TemporaryRedirect {
         * Constructor.
         */
        public TemporaryRedirect() {
-               this(null);
+               this((URI)null);
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param location <code>Location</code> header value.
+        */
+       public TemporaryRedirect(String location) {
+               this.location = URI.create(location);
        }
 
        /**
@@ -57,7 +66,7 @@ public class TemporaryRedirect {
        /**
         * @return <code>Location</code> header value.
         */
-       @Header(name="Location", description="")
+       @Header(name="Location")
        public URI getLocation() {
                return location;
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/ResponseAnnotationTest.java
 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/ResponseAnnotationTest.java
index 224f3e7..02e623f 100644
--- 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/ResponseAnnotationTest.java
+++ 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/annotation/ResponseAnnotationTest.java
@@ -246,7 +246,7 @@ public class ResponseAnnotationTest {
 
        @Test
        public void b06_dontUseOnThrown() throws Exception {
-               
b.get("/dontUseOnThrown").execute().assertStatus(500).assertBodyContains("HTTP 
500: Internal Server Error");
+               
b.get("/dontUseOnThrown").execute().assertStatus(500).assertBodyContains("foo");
        }
 
        @Test
@@ -364,7 +364,7 @@ public class ResponseAnnotationTest {
 
        @Test
        public void c06_dontUseOnThrown() throws Exception {
-               
c.get("/dontUseOnThrown").execute().assertStatus(500).assertBodyContains("HTTP 
500: Internal Server Error");
+               
c.get("/dontUseOnThrown").execute().assertStatus(500).assertBodyContains("foo");
        }
 
        @Test
diff --git 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/response/BasicTest.java
 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/response/BasicTest.java
index f301eaa..cbe5d74 100644
--- 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/response/BasicTest.java
+++ 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/response/BasicTest.java
@@ -21,6 +21,10 @@ import org.junit.runners.*;
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class BasicTest {
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Basic sanity tests
+       
//-----------------------------------------------------------------------------------------------------------------
+
        @RestResource
        public static class A {
                @RestMethod public Accepted accepted() { return new Accepted(); 
}
@@ -137,4 +141,35 @@ public class BasicTest {
        public void a22_useProxy() throws Exception {
                a.get("/useProxy").execute().assertStatus(305).assertBody("Use 
Proxy");
        }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Statuses with URIs.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @RestResource
+       public static class B {
+               @RestMethod public MovedPermanently movedPermanently() { return 
new MovedPermanently("servlet:/foo"); }
+               @RestMethod public PermanentRedirect permanentRedirect() { 
return new PermanentRedirect("servlet:/foo"); }
+               @RestMethod public SeeOther seeOther() { return new 
SeeOther("servlet:/foo"); }
+               @RestMethod public TemporaryRedirect temporaryRedirect() { 
return new TemporaryRedirect("servlet:/foo"); }
+       }
+
+       static MockRest b = MockRest.create(B.class);
+
+       @Test
+       public void b01_movedPermanently() throws Exception {
+               
b.get("/movedPermanently").execute().assertStatus(301).assertBody("Moved 
Permanently").assertHeader("Location", "/foo");
+       }
+       @Test
+       public void b02_permanentRedirect() throws Exception {
+               
b.get("/permanentRedirect").execute().assertStatus(308).assertBody("Permanent 
Redirect").assertHeader("Location", "/foo");
+       }
+       @Test
+       public void b03_seeOther() throws Exception {
+               b.get("/seeOther").execute().assertStatus(303).assertBody("See 
Other").assertHeader("Location", "/foo");
+       }
+       @Test
+       public void b04_temporaryRedirect() throws Exception {
+               
b.get("/temporaryRedirect").execute().assertStatus(307).assertBody("Temporary 
Redirect").assertHeader("Location", "/foo");
+       }
 }

Reply via email to