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 098e525  REST refactoring.
098e525 is described below

commit 098e525cab84f5fd5c14b78d44cb06f5bc527210
Author: JamesBognar <james.bog...@salesforce.com>
AuthorDate: Thu Mar 11 11:29:18 2021 -0500

    REST refactoring.
---
 .../apache/juneau/serializer/SerializerMatch.java  |   8 +-
 .../org/apache/juneau/rest/ResponseProcessor.java  |  60 ++---
 .../main/java/org/apache/juneau/rest/RestCall.java |  90 +------
 .../java/org/apache/juneau/rest/RestContext.java   |  33 ++-
 .../org/apache/juneau/rest/RestContextBuilder.java |   4 +-
 .../java/org/apache/juneau/rest/RestResponse.java  | 228 ++++++++++++-----
 .../juneau/rest/RrpcRestOperationContext.java      |   4 +-
 .../apache/juneau/rest/args/ResponseBeanArg.java   |   2 +-
 .../juneau/rest/processors/DefaultProcessor.java   | 276 ---------------------
 .../rest/processors/HttpEntityProcessor.java       |  14 +-
 .../rest/processors/HttpResourceProcessor.java     |  18 +-
 .../rest/processors/HttpResponseProcessor.java     |  22 +-
 .../rest/processors/InputStreamProcessor.java      |  15 +-
 ...oProcessor.java => PlainTextPojoProcessor.java} |  29 ++-
 .../juneau/rest/processors/ReaderProcessor.java    |  14 +-
 .../rest/processors/ResponseBeanProcessor.java     | 121 ++++++++-
 .../rest/processors/SerializedPojoProcessor.java   | 110 ++++++++
 .../juneau/rest/processors/ThrowableProcessor.java |  13 +-
 18 files changed, 550 insertions(+), 511 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerMatch.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerMatch.java
index f82c55b..eabfd20 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerMatch.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerMatch.java
@@ -22,7 +22,13 @@ public final class SerializerMatch {
        private final MediaType mediaType;
        private final Serializer serializer;
 
-       SerializerMatch(MediaType mediaType, Serializer serializer) {
+       /**
+        * Constructor.
+        *
+        * @param mediaType The media type matched.
+        * @param serializer The serializer matched.
+        */
+       public SerializerMatch(MediaType mediaType, Serializer serializer) {
                this.mediaType = mediaType;
                this.serializer = serializer;
        }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseProcessor.java
index 0fa888e..ccf3011 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ResponseProcessor.java
@@ -18,7 +18,6 @@ import javax.servlet.http.*;
 
 import org.apache.juneau.http.response.*;
 import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.processors.*;
 
 /**
  * Defines the interface for processors that convert POJOs to appropriate HTTP 
responses.
@@ -36,16 +35,6 @@ import org.apache.juneau.rest.processors.*;
  * </ul>
  *
  * <p>
- * By default, REST resources are registered with the following response 
processors:
- * <ul class='spaced-list'>
- *     <li class='jc'>{@link ReaderProcessor}
- *     <li class='jc'>{@link InputStreamProcessor}
- *     <li class='jc'>{@link HttpResourceProcessor}
- *     <li class='jc'>{@link HttpEntityProcessor}
- *     <li class='jc'>{@link DefaultProcessor}
- * </ul>
- *
- * <p>
  * Response processors can be used to process POJOs that cannot normally be 
handled through Juneau serializers, or
  * because it's simply easier to define response processors for special cases.
  *
@@ -55,7 +44,7 @@ import org.apache.juneau.rest.processors.*;
  * <p class='bcode w800'>
  *     <ja>@Rest</ja>(
  *             path=<js>"/example"</js>,
- *             responseHandlers=FooHandler.<jk>class</jk>
+ *             responseProcessors=FooProcessor.<jk>class</jk>
  *     )
  *     <jk>public class</jk> Example <jk>extends</jk> RestServlet {
  *
@@ -64,29 +53,44 @@ import org.apache.juneau.rest.processors.*;
  *                     <jk>return new</jk> Foo(<js>"123"</js>);
  *             }
  *
- *             <jk>public static class</jk> FooHandler <jk>implements</jk> 
ResponseHandler {
+ *             <jk>public static class</jk> FooProcessor <jk>implements</jk> 
ResponseProcessor {
  *                     <ja>@Override</ja>
- *                     <jk>public boolean</jk> handle(RestRequest 
<jv>req</jv>, RestResponse <jv>res</jv>, Object <jv>output</jv>) 
<jk>throws</jk> IOException, RestException {
- *                             <jk>if</jk> (<jv>output</jv> 
<jk>instanceof</jk> Foo) {
- *                                     Foo <jv>foo</jv> = (Foo)<jv>output</jv>;
- *                                     <jc>// Set some headers and body 
content.</jc>
- *                                     
<jv>res</jv>.setHeader(<js>"Foo-ID"</js>, <jv>foo</jv>.getId());
- *                                     
<jv>res</jv>.getWriter().write(<js>"foo.id="</js> + <jv>foo</jv>.getId());
- *                                     <jk>return true</jk>;  <jc>// We 
handled it.</jc>
- *                             }
- *                             <jk>return false</jk>;  <jc>// We didn't handle 
it.</jc>
+ *                     <jk>public int</jk> process(RestCall <jv>call</jv>) {
+ *
+ *                             RestResponse <jv>res</jv> = 
<jv>call</jv>.getRestResponse();
+ *                             Foo <jv>foo</jv> = 
<jv>res</jv>.getOutput(Foo.<jk>class</jk>);
+ *
+ *                             <jk>if</jk> (<jv>foo</jv> == <jk>null</jk>)
+ *                                     <jk>return</jk> <jsf>NEXT</jsf>;  
<jc>// Let the next processor handle it.</jc>
+ *
+ *                             <jc>// Set some headers and body content.</jc>
+ *                             <jv>res</jv>.setHeader(<js>"Foo-ID"</js>, 
<jv>foo</jv>.getId());
+ *                             
<jv>res</jv>.getWriter().write(<js>"foo.id="</js> + <jv>foo</jv>.getId());
+ *
+ *                             <jk>return</jk> <jsf>FINISHED</jsf>;  <jc>// We 
handled it.</jc>
  *                     }
  *             }
  *     }
  * </p>
- *
- * <ul class='seealso'>
- *     <li class='link'>{@doc RestmReturnTypes}
- * </ul>
  */
 public interface ResponseProcessor {
 
        /**
+        * Return code indicating to proceed to the next response processor in 
the chain.
+        */
+       public static final int NEXT = 0;
+
+       /**
+        * Return code indicating that processing is complete and to exit the 
chain.
+        */
+       public static final int FINISHED = 1;
+
+       /**
+        * Return code indicating to restart processing the chain from the 
beginning.
+        */
+       public static final int RESTART = 2;
+
+       /**
         * Process this response if possible.
         *
         * @param call The HTTP call.
@@ -99,10 +103,10 @@ public interface ResponseProcessor {
         *      </ul>
         * @throws IOException
         *      If low-level exception occurred on output stream.
-        *      Results in a {@link 
HttpServletResponse#SC_INTERNAL_SERVER_ERROR} error.
+        *      <br>Results in a {@link 
HttpServletResponse#SC_INTERNAL_SERVER_ERROR} error.
         * @throws BasicHttpException
         *      If some other exception occurred.
-        *      Can be used to provide an appropriate HTTP response code and 
message.
+        *      <br>Can be used to provide an appropriate HTTP response code 
and message.
         */
        int process(RestCall call) throws IOException, BasicHttpException;
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
index 211d9a3..0fa6d6e 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
@@ -12,8 +12,6 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.rest;
 
-import static java.util.Optional.*;
-
 import java.io.*;
 import java.lang.reflect.*;
 import java.util.*;
@@ -22,10 +20,7 @@ import javax.servlet.http.*;
 
 import org.apache.http.*;
 import org.apache.juneau.cp.*;
-import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.response.*;
-import org.apache.juneau.httppart.bean.*;
-import org.apache.juneau.reflect.*;
 import org.apache.juneau.rest.logging.*;
 import org.apache.juneau.rest.util.*;
 
@@ -208,52 +203,6 @@ public class RestCall {
        }
 
        /**
-        * Adds a header to the response.
-        *
-        * <p>
-        * If the header is a {@link BasicUriHeader}, the URI will be resolved 
using the {@link RestRequest#getUriResolver()} object.
-        *
-        * <p>
-        * If the header is a {@link SerializedHeader} and the serializer 
session is not set, it will be set to the one returned by {@link 
RestRequest#getPartSerializerSession()} before serialization.
-        *
-        * @param h The header to add.  Can be <jk>null</jk>.
-        * @return This object (for method chaining).
-        */
-       public RestCall addResponseHeader(Header h) {
-
-               if (h == null)
-                       return this;
-
-               if (h instanceof BasicUriHeader) {
-                       BasicUriHeader x = (BasicUriHeader)h;
-                       addResponseHeader(x.getName(), 
rreq.getUriResolver().resolve(x.getValue()));
-               } else if (h instanceof SerializedHeader) {
-                       SerializedHeader x = 
((SerializedHeader)h).copyWith(rreq.getPartSerializerSession(), null);
-                       addResponseHeader(x.getName(), 
rreq.getUriResolver().resolve(x.getValue()));
-               } else {
-                       addResponseHeader(h.getName(), h.getValue());
-               }
-
-               return this;
-       }
-
-       /**
-        * Adds a header to the response.
-        *
-        * <p>
-        * A no-op of either the name or value is <jk>null</jk>.
-        *
-        * @param name The header name.
-        * @param value The header value.
-        * @return This object (for method chaining).
-        */
-       public RestCall addResponseHeader(String name, String value) {
-               if (name != null && value != null)
-                       res.addHeader(name, value);
-               return this;
-       }
-
-       /**
         * Identifies that an exception occurred during this call.
         *
         * @param value The thrown exception.
@@ -266,18 +215,6 @@ public class RestCall {
        }
 
        /**
-        * Sets metadata about the response.
-        *
-        * @param value The metadata about the response.
-        * @return This object (for method chaining).
-        */
-       public RestCall responseMeta(ResponseBeanMeta value) {
-               if (rres != null)
-                       rres.setResponseMeta(value);
-               return this;
-       }
-
-       /**
         * Sets the output object to serialize as the response of this call.
         *
         * @param value The response output POJO.
@@ -343,15 +280,6 @@ public class RestCall {
        }
 
        /**
-        * Returns information about the output POJO.
-        *
-        * @return The metadata on the object returned by the REST Java method, 
or {@link ClassInfo#OBJECT} if nothing was set.
-        */
-       public ClassInfo getOutputInfo() {
-               return 
getRestResponse().getOutputInfo().orElse(ClassInfo.OBJECT);
-       }
-
-       /**
         * Returns the method context of this call.
         *
         * @return The method context of this call.
@@ -538,19 +466,21 @@ public class RestCall {
        }
 
        /**
-        * Returns the output that was set by calling {@link 
RestResponse#setOutput(Object)}.
+        * Returns <jk>true</jk> if the response contains output.
         *
         * <p>
-        * If it's empty, then {@link RestResponse#setOutput(Object)} wasn't 
called.
-        * <br>If it's not empty but contains an empty, then 
<c>response.setObject(<jk>null</jk>)</c> was called.
-        * <br>Otherwise, {@link RestResponse#setOutput(Object)} was called 
with a non-null value.
+        * This implies {@link RestResponse#setOutput(Object)} has been called 
on this object.
         *
-        * @return The output object.  Never <jk>null</jk>.
+        * <p>
+        * Note that this also returns <jk>true</jk> even if {@link 
RestResponse#setOutput(Object)} was called with a <jk>null</jk>
+        * value as this means the response contains an output value of 
<jk>null</jk> as opposed to no value at all.
+        *
+        * @return <jk>true</jk> if the response contains output.
         */
-       public Optional<Optional<Object>> getOutput() {
+       public boolean hasOutput() {
                if (rres != null)
-                       return rres.getOutput();
-               return rres == null ? empty() : rres.getOutput();
+                       return rres.hasOutput();
+               return false;
        }
 
        /**
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 c45fbb1..3f37ccc 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
@@ -18,6 +18,7 @@ import static org.apache.juneau.http.HttpHeaders.*;
 import static org.apache.juneau.internal.IOUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.rest.HttpRuntimeException.*;
+import static org.apache.juneau.rest.ResponseProcessor.*;
 import static org.apache.juneau.rest.logging.RestLoggingDetail.*;
 import static java.util.Collections.*;
 import static java.util.logging.Level.*;
@@ -2293,29 +2294,39 @@ public class RestContext extends BeanContext {
         * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP 
responses.
         *
         * <p>
-        * By default, the following response handlers are provided 
out-of-the-box:
+        * By default, the following response handlers are provided in the 
specified order:
         * <ul>
         *      <li class='jc'>{@link ReaderProcessor}
         *      <li class='jc'>{@link InputStreamProcessor}
+        *      <li class='jc'>{@link ThrowableProcessor}
+        *      <li class='jc'>{@link HttpResponseProcessor}
         *      <li class='jc'>{@link HttpResourceProcessor}
         *      <li class='jc'>{@link HttpEntityProcessor}
-        *      <li class='jc'>{@link DefaultProcessor}
+        *      <li class='jc'>{@link ResponseBeanProcessor}
+        *      <li class='jc'>{@link PlainTextPojoProcessor}
+        *      <li class='jc'>{@link SerializedPojoProcessor}
         * </ul>
         *
         * <h5 class='section'>Example:</h5>
         * <p class='bcode w800'>
-        *      <jc>// Our custom response processor for MySpecialObject 
objects. </jc>
+        *      <jc>// Our custom response processor for Foo objects. </jc>
         *      <jk>public class</jk> MyResponseProcessor <jk>implements</jk> 
ResponseProcessor {
         *
         *              <ja>@Override</ja>
-        *              <jk>public boolean</jk> process(RestRequest 
<jv>req</jv>, RestResponse <jv>res</jv>, Object <jv>output</jv>) 
<jk>throws</jk> IOException, RestException {
-        *                      <jk>if</jk> (output <jk>instanceof</jk> 
MySpecialObject) {
+        *              <jk>public int</jk> process(RestCall <jv>call</jv>) 
<jk>throws</jk> IOException {
+        * 
+        *                              RestResponse <jv>res</jv> = 
<jv>call</jv>.getRestResponse();
+        *                              Foo <jv>foo</jv> = 
<jv>res</jv>.getOutput(Foo.<jk>class</jk>);
+        * 
+        *                              <jk>if</jk> (<jv>foo</jv> == 
<jk>null</jk>)
+        *                                      <jk>return</jk> 
<jsf>NEXT</jsf>;  <jc>// Let the next processor handle it.</jc>
+        * 
         *                              <jk>try</jk> (Writer <jv>w</jv> = 
<jv>res</jv>.getNegotiatedWriter()) {
         *                                      <jc>//Pipe it to the writer 
ourselves.</jc>
         *                              }
-        *                              <jk>return true</jk>;  <jc>// We 
handled it.</jc>
-        *                      }
-        *                      <jk>return false</jk>; <jc>// We didn't handle 
it.</jc>
+        *                              
+        *                              <jk>return</jk> <jsf>FINISHED</jsf>;  
<jc>// We handled it.</jc>
+        *                      }
         *              }
         *      }
         *
@@ -6730,7 +6741,7 @@ public class RestContext extends BeanContext {
                                handleNotFound(call);
                        }
 
-                       if (call.getOutput().isPresent()) {
+                       if (call.hasOutput()) {
                                // Now serialize the output if there was any.
                                // Some subclasses may write to the 
OutputStream or Writer directly.
                                processResponse(call);
@@ -6785,9 +6796,9 @@ public class RestContext extends BeanContext {
                int loops = 5;
                for (int i = 0; i < l.size(); i++) {
                        int j = l.get(i).process(call);
-                       if (j == 1)
+                       if (j == FINISHED)
                                return;
-                       if (j == 2) {
+                       if (j == RESTART) {
                                if (loops-- < 0)
                                        throw new InternalServerError("Too many 
processing loops.");
                                i = -1;  // Start over.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
index 1f13dba..81c225c 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
@@ -133,8 +133,8 @@ public class RestContextBuilder extends BeanContextBuilder 
implements ServletCon
                                HttpResourceProcessor.class,
                                HttpEntityProcessor.class,
                                ResponseBeanProcessor.class,
-                               PojoProcessor.class,
-                               DefaultProcessor.class
+                               PlainTextPojoProcessor.class,
+                               SerializedPojoProcessor.class
                        );
 
                        // Pass-through default values.
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 46bdb4e..4e3afd5 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
@@ -29,7 +29,7 @@ import org.apache.juneau.collections.*;
 import org.apache.juneau.encoders.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.httppart.bean.*;
-import org.apache.juneau.reflect.*;
+import org.apache.juneau.oapi.*;
 import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.response.*;
 import org.apache.juneau.rest.logging.*;
@@ -65,11 +65,16 @@ public final class RestResponse {
        private final RestRequest request;
 
        private Optional<Optional<Object>> output = empty();  // The POJO being 
sent to the output.
-       private Optional<ClassInfo> outputInfo = empty();
        private ServletOutputStream sos;
        private FinishableServletOutputStream os;
        private FinishablePrintWriter w;
-       private ResponseBeanMeta responseMeta;
+       private ResponseBeanMeta responseBeanMeta;
+       private RestOperationContext opContext;
+       private Optional<HttpPartSchema> bodySchema;
+       private Serializer serializer;
+       private Optional<SerializerMatch> serializerMatch;
+       private boolean safeHeaders;
+       private int maxHeaderLength = 8096;
 
        /**
         * Constructor.
@@ -79,8 +84,8 @@ public final class RestResponse {
                inner = call.getResponse();
                request = call.getRestRequest();
 
-               RestOperationContext opContext = call.getRestOperationContext();
-               responseMeta = opContext.getResponseMeta();
+               opContext = call.getRestOperationContext();
+               responseBeanMeta = opContext.getResponseMeta();
 
                RestContext context = call.getContext();
 
@@ -90,7 +95,7 @@ public final class RestResponse {
                                HttpPartParser p = context.getPartParser();
                                OMap m = 
p.createPartSession(request.getParserSessionArgs()).parse(HEADER, null, 
passThroughHeaders, context.getClassMeta(OMap.class));
                                for (Map.Entry<String,Object> e : m.entrySet())
-                                       setHeaderSafe(e.getKey(), 
e.getValue().toString());
+                                       addHeader(e.getKey(), 
resolveUris(e.getValue()));
                        }
                } catch (Exception e1) {
                        throw new BadRequest(e1, "Invalid format for header 
'x-response-headers'.  Must be in URL-encoded format.");
@@ -113,9 +118,9 @@ public final class RestResponse {
                }
 
                for (Header e : 
request.getContext().getDefaultResponseHeaders().getAll())
-                       setHeaderSafe(e.getName(), stringify(e.getValue()));
+                       addHeader(e.getName(), resolveUris(e.getValue()));
                for (Header e : opContext.getDefaultResponseHeaders().getAll())
-                       setHeaderSafe(e.getName(), stringify(e.getValue()));
+                       addHeader(e.getName(), resolveUris(e.getValue()));
 
                if (charset == null)
                        throw new NotAcceptable("No supported charsets in 
header ''Accept-Charset'': ''{0}''", 
request.getHeader("Accept-Charset").orElse(null));
@@ -176,7 +181,6 @@ public final class RestResponse {
         */
        public RestResponse setOutput(Object output) {
                this.output = of(ofNullable(output));
-               this.outputInfo = ofNullable(ClassInfo.ofc(output));
                return this;
        }
 
@@ -216,12 +220,19 @@ public final class RestResponse {
        }
 
        /**
-        * Returns metadata about the output object.
+        * Returns <jk>true</jk> if the response contains output.
         *
-        * @return Metadata about the output object.
+        * <p>
+        * This implies {@link #setOutput(Object)} has been called on this 
object.
+        *
+        * <p>
+        * Note that this also returns <jk>true</jk> even if {@link 
#setOutput(Object)} was called with a <jk>null</jk>
+        * value as this means the response contains an output value of 
<jk>null</jk> as opposed to no value at all.
+        *
+        * @return <jk>true</jk> if the response contains output.
         */
-       public Optional<ClassInfo> getOutputInfo() {
-               return outputInfo;
+       public boolean hasOutput() {
+               return output.isPresent();
        }
 
        /**
@@ -505,6 +516,9 @@ public final class RestResponse {
                        if (ct != null && ct.getParameter("charset") != null)
                                
inner.setCharacterEncoding(ct.getParameter("charset"));
                } else {
+                       if (safeHeaders)
+                               value = stripInvalidHttpHeaderChars(value);
+                       value = abbreviate(value, maxHeaderLength);
                        inner.setHeader(name, value);
                }
        }
@@ -520,40 +534,6 @@ public final class RestResponse {
        }
 
        /**
-        * Same as {@link #setHeader(String, String)} but strips invalid 
characters from the value if present.
-        *
-        * These include CTRL characters, newlines, and non-ISO8859-1 
characters.
-        * Also limits the string length to 1024 characters.
-        *
-        * @param name Header name.
-        * @param value Header value.
-        */
-       public void setHeaderSafe(String name, String value) {
-               setHeaderSafe(name, value, 1024);
-       }
-
-       /**
-        * Same as {@link #setHeader(String, String)} but strips invalid 
characters from the value if present.
-        *
-        * These include CTRL characters, newlines, and non-ISO8859-1 
characters.
-        *
-        * @param name Header name.
-        * @param value Header value.
-        * @param maxLength
-        *      The maximum length of the header value.
-        *      Will be truncated with <js>"..."</js> added if the value 
exceeds the length.
-        */
-       public void setHeaderSafe(String name, String value, int maxLength) {
-
-               // Jetty doesn't set the content type correctly if set through 
this method.
-               // Tomcat/WAS does.
-               if (name.equalsIgnoreCase("Content-Type"))
-                       inner.setContentType(value);
-               else
-                       inner.setHeader(name, 
abbreviate(stripInvalidHttpHeaderChars(value), maxLength));
-       }
-
-       /**
         * Sets a header on the request.
         *
         * @param name The header name.
@@ -628,6 +608,20 @@ public final class RestResponse {
        }
 
        /**
+        * Specifies the schema for the response body.
+        *
+        * <p>
+        * Used by schema-aware serializers such as {@link OpenApiSerializer}.  
Ignored by other serializers.
+        *
+        * @param schema The body schema
+        * @return This object (for method chaining).
+        */
+       public RestResponse bodySchema(HttpPartSchema schema) {
+               this.bodySchema = ofNullable(schema);
+               return this;
+       }
+
+       /**
         * Same as {@link #setHeader(String, String)} but header is defined as 
a response part
         *
         * @param h Header to set.
@@ -635,7 +629,7 @@ public final class RestResponse {
         * @throws SerializeException Header part could not be serialized.
         */
        public void setHeader(HttpPart h) throws SchemaValidationException, 
SerializeException {
-               setHeaderSafe(h.getName(), h.getValue());
+               setHeader(h.getName(), h.getValue());
        }
 
        /**
@@ -709,8 +703,8 @@ public final class RestResponse {
         *      The metadata about this response.
         *      <jk>Never <jk>null</jk>.
         */
-       public ResponseBeanMeta getResponseMeta() {
-               return responseMeta;
+       public ResponseBeanMeta getResponseBeanMeta() {
+               return responseBeanMeta;
        }
 
        /**
@@ -719,8 +713,8 @@ public final class RestResponse {
         * @param rbm The metadata about this response.
         * @return This object (for method chaining).
         */
-       public RestResponse setResponseMeta(ResponseBeanMeta rbm) {
-               this.responseMeta = rbm;
+       public RestResponse setResponseBeanMeta(ResponseBeanMeta rbm) {
+               this.responseBeanMeta = rbm;
                return this;
        }
 
@@ -738,11 +732,13 @@ public final class RestResponse {
         * Returns this value cast to the specified class.
         *
         * @param c The class to cast to.
-        * @return This value cast to the specified class.
+        * @return This value cast to the specified class, or <jk>null</jk> if 
the object doesn't exist or isn't the specified type.
         */
        @SuppressWarnings("unchecked")
        public <T> T getOutput(Class<T> c) {
-               return (T)getRawOutput();
+               if (isOutputType(c))
+                       return (T)getRawOutput();
+               return null;
        }
 
        /**
@@ -808,17 +804,61 @@ public final class RestResponse {
        }
 
        /**
+        * Enabled safe-header mode.
+        *
+        * <p>
+        * When enabled, invalid characters such as CTRL characters will be 
stripped from header values
+        * before they get set.
+        *
+        * @return This object (for method chaining).
+        */
+       public RestResponse safeHeaders() {
+               this.safeHeaders = true;
+               return this;
+       }
+
+       /**
+        * Specifies the maximum length for header values.
+        *
+        * <p>
+        * Header values that exceed this length will get truncated.
+        *
+        * @param value The new value for this setting.  The default is 
<c>8096</c>.
+        * @return This object (for method chaining).
+        */
+       public RestResponse maxHeaderLength(int value) {
+               this.maxHeaderLength = value;
+               return this;
+       }
+
+       /**
         * Adds a response header with the given name and value.
         *
         * <p>
         * This method allows response headers to have multiple values.
         *
+        * <p>
+        * A no-op of either the name or value is <jk>null</jk>.
+        *
+        * <p>
+        * Note that per <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2'>RFC2616</a>,
+        * only headers defined as comma-delimited lists [i.e., #(values)] 
should be defined as multiple message header fields.
+        *
         * @param name The header name.
-        * @param value The header value.
+        * @param value The header value.
         * @return This object (for method chaining).
         */
        public RestResponse addHeader(String name, String value) {
-               inner.addHeader(name, value);
+               if (name != null && value != null) {
+                       if (name.equalsIgnoreCase("Content-Type"))
+                               setHeader(name, value);
+                       else {
+                               if (safeHeaders)
+                                       value = 
stripInvalidHttpHeaderChars(value);
+                               value = abbreviate(value, maxHeaderLength);
+                               inner.addHeader(name, value);
+                       }
+               }
                return this;
        }
 
@@ -835,7 +875,17 @@ public final class RestResponse {
         * @return This object (for method chaining).
         */
        public RestResponse setHeader(Header header) {
-               inner.setHeader(header.getName(), header.getValue());
+               if (header == null) {
+                       // Do nothing.
+               } else if (header instanceof BasicUriHeader) {
+                       BasicUriHeader x = (BasicUriHeader)header;
+                       setHeader(x.getName(), resolveUris(x.getValue()));
+               } else if (header instanceof SerializedHeader) {
+                       SerializedHeader x = 
((SerializedHeader)header).copyWith(request.getPartSerializerSession(), null);
+                       setHeader(x.getName(), resolveUris(x.getValue()));
+               } else {
+                       setHeader(header.getName(), header.getValue());
+               }
                return this;
        }
 
@@ -848,14 +898,39 @@ public final class RestResponse {
         * <p>
         * Value is added at the end of the headers.
         *
+        * <p>
+        * If the header is a {@link BasicUriHeader}, the URI will be resolved 
using the {@link RestRequest#getUriResolver()} object.
+        *
+        * <p>
+        * If the header is a {@link SerializedHeader} and the serializer 
session is not set, it will be set to the one returned by {@link 
RestRequest#getPartSerializerSession()} before serialization.
+        *
+        * <p>
+        * Note that per <a class='doclink' 
href='https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2'>RFC2616</a>,
+        * only headers defined as comma-delimited lists [i.e., #(values)] 
should be defined as multiple message header fields.
+        *
         * @param header The header.
         * @return This object (for method chaining).
         */
        public RestResponse addHeader(Header header) {
-               inner.addHeader(header.getName(), header.getValue());
+               if (header == null) {
+                       // Do nothing.
+               } else if (header instanceof BasicUriHeader) {
+                       BasicUriHeader x = (BasicUriHeader)header;
+                       addHeader(x.getName(), resolveUris(x.getValue()));
+               } else if (header instanceof SerializedHeader) {
+                       SerializedHeader x = 
((SerializedHeader)header).copyWith(request.getPartSerializerSession(), null);
+                       addHeader(x.getName(), resolveUris(x.getValue()));
+               } else {
+                       addHeader(header.getName(), header.getValue());
+               }
                return this;
        }
 
+       private String resolveUris(Object value) {
+               String s = stringify(value);
+               return request.getUriResolver().resolve(s);
+       }
+
        /**
         * Gets the value of the response header with the given name.
         *
@@ -868,4 +943,41 @@ public final class RestResponse {
        public String getHeader(String name) {
                return inner.getHeader(name);
        }
+
+
+       /**
+        * Returns the matching serializer and media type for this response.
+        *
+        * @return The matching serializer, never <jk>null</jk>.
+        */
+       public Optional<SerializerMatch> getSerializerMatch() {
+               if (serializerMatch != null)
+                       return serializerMatch;
+               if (serializer != null) {
+                       serializerMatch = of(new 
SerializerMatch(getMediaType(), serializer));
+               } else {
+                       serializerMatch = 
ofNullable(opContext.getSerializers().getSerializerMatch(request.getHeader("Accept").orElse("*/*")));
+               }
+               return serializerMatch;
+       }
+
+       /**
+        * Returns the schema of the response body.
+        *
+        * @return The schema of the response body, never <jk>null</jk>.
+        */
+       public Optional<HttpPartSchema> getBodySchema() {
+               if (bodySchema != null)
+                       return bodySchema;
+               if (responseBeanMeta != null)
+                       bodySchema = ofNullable(responseBeanMeta.getSchema());
+               else {
+                       ResponseBeanMeta rbm = 
opContext.getResponseBeanMeta(getOutput(Object.class));
+                       if (rbm != null)
+                               bodySchema = ofNullable(rbm.getSchema());
+                       else
+                               bodySchema = empty();
+               }
+               return bodySchema;
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestOperationContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestOperationContext.java
index 7a8b45e..b10e738 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestOperationContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestOperationContext.java
@@ -17,7 +17,6 @@ import static org.apache.juneau.rest.HttpRuntimeException.*;
 
 import java.io.*;
 import java.lang.reflect.*;
-import java.util.*;
 
 import javax.servlet.*;
 
@@ -54,8 +53,7 @@ public class RrpcRestOperationContext extends 
RestOperationContext {
 
                super.invoke(call);
 
-               Optional<Optional<Object>> x = call.getOutput();
-               final Object o = x.isPresent() ? x.get().orElse(null) : null;
+               final Object o = call.hasOutput() ? 
call.getRestResponse().getOutput(Object.class) : null;
 
                if ("GET".equals(call.getMethod())) {
                        call.output(meta.getMethodsByPath().keySet());
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/ResponseBeanArg.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/ResponseBeanArg.java
index afc383e..370a2cc 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/ResponseBeanArg.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/args/ResponseBeanArg.java
@@ -71,7 +71,7 @@ public class ResponseBeanArg implements RestOperationArg {
                                ResponseBeanMeta meta = 
req.getOpContext().getResponseBeanMeta(o);
                                if (meta == null)
                                        meta = ResponseBeanArg.this.meta;
-                               res.setResponseMeta(meta);
+                               res.setResponseBeanMeta(meta);
                                res.setOutput(o);
                        }
                });
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/DefaultProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/DefaultProcessor.java
deleted file mode 100644
index 972118e..0000000
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/DefaultProcessor.java
+++ /dev/null
@@ -1,276 +0,0 @@
-// 
***************************************************************************************************************************
-// * 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.processors;
-
-import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.httppart.HttpPartType.*;
-import static org.apache.juneau.internal.IOUtils.*;
-
-import java.io.*;
-import java.lang.reflect.*;
-import java.util.*;
-
-import org.apache.http.*;
-import org.apache.juneau.httppart.*;
-import org.apache.juneau.httppart.bean.*;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.rest.*;
-import org.apache.juneau.http.header.*;
-import org.apache.juneau.http.part.*;
-import org.apache.juneau.http.response.*;
-import org.apache.juneau.rest.util.FinishablePrintWriter;
-import org.apache.juneau.rest.util.FinishableServletOutputStream;
-import org.apache.juneau.serializer.*;
-
-/**
- * Response processor for POJOs not handled by other processors.
- *
- * <p>
- * This uses the serializers defined on the response to serialize the POJO.
- *
- * <p>
- * The {@link Serializer} used is based on the <c>Accept</c> header on the 
request.
- *
- * <p>
- * The <c>Content-Type</c> header is set to the mime-type defined on the 
selected serializer based on the
- * <c>produces</c> value passed in through the constructor.
- *
- * <ul class='seealso'>
- *     <li class='link'>{@doc RestmReturnTypes}
- * </ul>
- */
-public class DefaultProcessor implements ResponseProcessor {
-
-       @SuppressWarnings("resource")
-       @Override /* ResponseHandler */
-       public int process(RestCall call) throws IOException, 
InternalServerError, NotAcceptable {
-               RestRequest req = call.getRestRequest();
-               RestResponse res = call.getRestResponse();
-               SerializerGroup g = res.getOpContext().getSerializers();
-               String accept = req.getHeader("Accept").orElse("*/*");
-               SerializerMatch sm = g.getSerializerMatch(accept);
-               HttpPartSchema schema = null;
-               HttpPartSerializerSession ps = req.getPartSerializerSession();
-
-               Optional<Optional<Object>> output = res.getOutput();
-               Object o = output.isPresent() ? output.get().orElse(null) : 
null;
-
-               ResponseBeanMeta rm = res.getResponseMeta();
-               if (rm == null)
-                       rm = req.getOpContext().getResponseBeanMeta(o);
-
-               if (rm != null) {
-
-                       boolean isThrowable = 
rm.getClassMeta().isChildOf(Throwable.class);
-                       if (isThrowable && o != null) {
-                               Throwable t = (Throwable)o;
-                               res.setHeader(Thrown.of(t));
-                               if (req.isDebug())
-                                       t.printStackTrace();
-                       }
-
-                       ResponseBeanPropertyMeta stm = rm.getStatusMethod();
-                       if (stm != null) {
-                               try {
-                                       
res.setStatus((int)stm.getGetter().invoke(o));
-                               } catch (Exception e) {
-                                       throw new InternalServerError(e, "Could 
not get status.");
-                               }
-                       } else if (rm.getCode() != 0) {
-                               res.setStatus(rm.getCode());
-                       }
-
-                       for (ResponseBeanPropertyMeta hm : 
rm.getHeaderMethods()) {
-                               String n = hm.getPartName().orElse(null);
-                               try {
-                                       Object ho = hm.getGetter().invoke(o);
-                                       HttpPartSchema partSchema = 
hm.getSchema();
-                                       if ("*".equals(n)) {
-                                               for (Object ho2 : iterate(ho)) {
-                                                       if (ho2 instanceof 
Map.Entry) {
-                                                               
@SuppressWarnings("rawtypes")
-                                                               Map.Entry x = 
(Map.Entry)ho2;
-                                                               String k = 
stringify(x.getKey());
-                                                               Object v = 
x.getValue();
-                                                               
res.setHeader(new HttpPart(k, RESPONSE_HEADER, partSchema.getProperty(k), 
hm.getSerializerSession().orElse(ps), v));
-                                                       } else if (ho2 
instanceof SerializedHeader) {
-                                                               
SerializedHeader x = ((SerializedHeader)ho2);
-                                                               x = 
x.copyWith(ps, partSchema.getProperty(x.getName()));
-                                                               
res.setHeader(x.getName(), x.getValue());
-                                                       } else if (ho2 
instanceof SerializedPart) {
-                                                               SerializedPart 
x = ((SerializedPart)ho2);
-                                                               x = 
x.copyWith(ps, partSchema.getProperty(x.getName()));
-                                                               
res.setHeader(x.getName(), x.getValue());
-                                                       } else if (ho2 
instanceof BasicUriHeader) {
-                                                               BasicUriHeader 
x = (BasicUriHeader)ho2;
-                                                               
res.setHeader(x.getName(), req.getUriResolver().resolve(x.getValue()));
-                                                       } else if (ho2 
instanceof Header) {
-                                                               Header x = 
(Header)ho2;
-                                                               
res.setHeader(x.getName(), x.getValue());
-                                                       } else if (ho2 
instanceof NameValuePair) {
-                                                               NameValuePair x 
= (NameValuePair)ho2;
-                                                               
res.setHeader(x.getName(), x.getValue());
-                                                       } else {
-                                                               throw new 
InternalServerError("Invalid type ''{0}'' for header ''{1}''", ho2 == null ? 
null : ho2.getClass().getName(), n);
-                                                       }
-                                               }
-                                       } else {
-                                               if (ho instanceof 
SerializedHeader) {
-                                                       SerializedHeader x = 
((SerializedHeader)ho).copyWith(ps, schema);
-                                                       
res.setHeader(x.getName(), x.getValue());
-                                               } else if (ho instanceof 
SerializedPart) {
-                                                       SerializedPart x = 
((SerializedPart)ho).copyWith(ps, schema);
-                                                       
res.setHeader(x.getName(), x.getValue());
-                                               } else if (ho instanceof 
Header) {
-                                                       Header x = (Header)ho;
-                                                       
res.setHeader(x.getName(), x.getValue());
-                                               } else if (ho instanceof 
NameValuePair) {
-                                                       NameValuePair x = 
(NameValuePair)ho;
-                                                       
res.setHeader(x.getName(), x.getValue());
-                                               } else {
-                                                       res.setHeader(new 
HttpPart(n, RESPONSE_HEADER, hm.getSchema(), 
hm.getSerializerSession().orElse(ps), ho));
-                                               }
-                                       }
-                               } catch (Exception e) {
-                                       throw new InternalServerError(e, "Could 
not set header ''{0}''", n);
-                               }
-                       }
-
-                       ResponseBeanPropertyMeta bm = rm.getBodyMethod();
-
-                       if (bm != null) {
-                               Method m = bm.getGetter();
-                               try {
-                                       Class<?>[] pt = m.getParameterTypes();
-                                       if (pt.length == 1) {
-                                               Class<?> ptt = pt[0];
-                                               if (ptt == OutputStream.class)
-                                                       m.invoke(o, 
res.getOutputStream());
-                                               else if (ptt == Writer.class)
-                                                       m.invoke(o, 
res.getWriter());
-                                               return 1;
-                                       }
-                                       o = m.invoke(o);
-                               } catch (Exception e) {
-                                       throw new InternalServerError(e, "Could 
not get body.");
-                               }
-                       }
-
-                       schema = rm.getSchema();
-               }
-
-               if (sm != null) {
-                       Serializer s = sm.getSerializer();
-                       MediaType mediaType = res.getMediaType();
-                       if (mediaType == null)
-                               mediaType = sm.getMediaType();
-
-                       MediaType responseType = s.getResponseContentType();
-                       if (responseType == null)
-                               responseType = mediaType;
-
-                       res.setContentType(responseType.toString());
-
-                       try {
-                               if (req.isPlainText())
-                                       res.setContentType("text/plain");
-                               SerializerSession session = s.createSession(
-                                       SerializerSessionArgs
-                                               .create()
-                                               
.properties(req.getAttributes().asMap())
-                                               
.javaMethod(req.getOpContext().getJavaMethod())
-                                               .locale(req.getLocale())
-                                               
.timeZone(req.getTimeZone().orElse(null))
-                                               .mediaType(mediaType)
-                                               .streamCharset(res.getCharset())
-                                               .schema(schema)
-                                               .debug(req.isDebug() ? true : 
null)
-                                               .uriContext(req.getUriContext())
-                                               
.useWhitespace(req.isPlainText() ? true : null)
-                                               
.resolver(req.getVarResolverSession())
-                               );
-
-                               for (Map.Entry<String,String> h : 
session.getResponseHeaders().entrySet())
-                                       res.setHeaderSafe(h.getKey(), 
h.getValue());
-
-                               if (! session.isWriterSerializer()) {
-                                       if (req.isPlainText()) {
-                                               FinishablePrintWriter w = 
res.getNegotiatedWriter();
-                                               ByteArrayOutputStream baos = 
new ByteArrayOutputStream();
-                                               session.serialize(o, baos);
-                                               
w.write(StringUtils.toSpacedHex(baos.toByteArray()));
-                                               w.flush();
-                                               w.finish();
-                                       } else {
-                                               FinishableServletOutputStream 
os = res.getNegotiatedOutputStream();
-                                               session.serialize(o, os);
-                                               os.flush();
-                                               os.finish();
-                                       }
-                               } else {
-                                       FinishablePrintWriter w = 
res.getNegotiatedWriter();
-                                       session.serialize(o, w);
-                                       w.flush();
-                                       w.finish();
-                               }
-                       } catch (SerializeException e) {
-                               throw new InternalServerError(e);
-                       }
-                       return 1;
-               }
-
-               // Non-existent Accept or plain/text can just be serialized 
as-is.
-               if (o != null && (isEmpty(accept) || 
accept.startsWith("text/plain") || accept.contains("*/*"))) {
-                       String out = null;
-                       if (isEmpty(res.getContentType()))
-                               res.setContentType("text/plain");
-                       if (o instanceof InputStream) {
-                               try (OutputStream os = 
res.getNegotiatedOutputStream()) {
-                                       pipe((InputStream)o, os);
-                               }
-                       } else if (o instanceof Reader) {
-                               try (FinishablePrintWriter w = 
res.getNegotiatedWriter()) {
-                                       pipe((Reader)o, w);
-                                       w.finish();
-                               }
-                       } else {
-                               out = 
req.getBeanSession().getClassMetaForObject(o).toString(o);
-                               FinishablePrintWriter w = 
res.getNegotiatedWriter();
-                               w.append(out);
-                               w.flush();
-                               w.finish();
-                       }
-                       return 1;
-               }
-
-               if (o == null)
-                       return 1;
-
-               throw new NotAcceptable(
-                       "Unsupported media-type in request header ''Accept'': 
''{0}''\n\tSupported media-types: {1}",
-                       req.getHeader("Accept").orElse(""), 
g.getSupportedMediaTypes()
-               );
-       }
-
-       private Iterable<?> iterate(Object o) {
-               if (o == null)
-                       return Collections.emptyList();
-               if (o instanceof Map)
-                       return ((Map<?,?>)o).entrySet();
-               if (o.getClass().isArray())
-                       return Arrays.asList((Object[])o);
-               if (o instanceof Collection)
-                       return (Collection<?>)o;
-               throw new InternalServerError("Could not iterate over Headers 
of type ''{0}''", o.getClass().getName());
-       }
- }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpEntityProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpEntityProcessor.java
index 7ab555a..93f5787 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpEntityProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpEntityProcessor.java
@@ -27,24 +27,24 @@ public final class HttpEntityProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().isChildOf(HttpEntity.class))
-                       return 0;
-
                RestResponse res = call.getRestResponse();
                HttpEntity e = res.getOutput(HttpEntity.class);
 
-               call.addResponseHeader(e.getContentType());
-               call.addResponseHeader(e.getContentEncoding());
+               if (e == null)
+                       return NEXT;
+
+               res.setHeader(e.getContentType());
+               res.setHeader(e.getContentEncoding());
                long contentLength = e.getContentLength();
                if (contentLength >= 0)
-                       call.addResponseHeader(contentLength(contentLength));
+                       res.setHeader(contentLength(contentLength));
 
                try (OutputStream os = res.getNegotiatedOutputStream()) {
                        e.writeTo(os);
                        os.flush();
                }
 
-               return 1;
+               return FINISHED;
        }
 }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResourceProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResourceProcessor.java
index 9ddffef..a65c4d7 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResourceProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResourceProcessor.java
@@ -29,28 +29,28 @@ public final class HttpResourceProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().isChildOf(HttpResource.class))
-                       return 0;
-
                RestResponse res = call.getRestResponse();
                HttpResource r = res.getOutput(HttpResource.class);
 
-               call.addResponseHeader(r.getContentType());
-               call.addResponseHeader(r.getContentEncoding());
+               if (r == null)
+                       return NEXT;
+
+               res.setHeader(r.getContentType());
+               res.setHeader(r.getContentEncoding());
                long contentLength = r.getContentLength();
                if (contentLength >= 0)
-                       call.addResponseHeader(contentLength(contentLength));
-               
+                       res.setHeader(contentLength(contentLength));
+
                List<Header> allHeaders = r.getAllHeaders();
                for (int i = 0; i < allHeaders.size() ; i++) // Avoids Iterator 
creation.
-                       call.addResponseHeader(allHeaders.get(i));
+                       res.addHeader(allHeaders.get(i));
 
                try (OutputStream os = res.getNegotiatedOutputStream()) {
                        r.writeTo(os);
                        os.flush();
                }
 
-               return 1;
+               return FINISHED;
        }
 }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResponseProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResponseProcessor.java
index 6d8f251..d3a7869 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResponseProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/HttpResponseProcessor.java
@@ -27,31 +27,31 @@ public final class HttpResponseProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().isChildOf(HttpResponse.class))
-                       return 0;
-
                RestResponse res = call.getRestResponse();
                HttpResponse r = res.getOutput(HttpResponse.class);
 
-               call.status(r.getStatusLine().getStatusCode());
+               if (r == null)
+                       return NEXT;
+
+               call.status(r.getStatusLine());
 
                HttpEntity e = r.getEntity();
 
-               call.addResponseHeader(e.getContentType());
-               call.addResponseHeader(e.getContentEncoding());
+               res.setHeader(e.getContentType());
+               res.setHeader(e.getContentEncoding());
                long contentLength = e.getContentLength();
                if (contentLength >= 0)
-                       call.addResponseHeader(contentLength(contentLength));
-               
+                       res.setHeader(contentLength(contentLength));
+
                for (Header h : r.getAllHeaders()) // No iterator involved.
-                       call.addResponseHeader(h);
+                       res.addHeader(h);
 
                try (OutputStream os = res.getNegotiatedOutputStream()) {
                        e.writeTo(os);
                        os.flush();
                }
-               
-               return 1;
+
+               return FINISHED;
        }
 }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/InputStreamProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/InputStreamProcessor.java
index 1a93e2f..067b24d 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/InputStreamProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/InputStreamProcessor.java
@@ -33,13 +33,16 @@ public final class InputStreamProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().isChildOf(InputStream.class))
-                       return 0;
-
                RestResponse res = call.getRestResponse();
-               try (InputStream is = res.getOutput(InputStream.class); 
OutputStream os = res.getNegotiatedOutputStream()) {
-                       pipe(is, os);
+               InputStream is = res.getOutput(InputStream.class);
+
+               if (is == null)
+                       return NEXT;
+
+               try (InputStream is2 = is; OutputStream os = 
res.getNegotiatedOutputStream()) {
+                       pipe(is2, os);
                }
-               return 1;
+
+               return FINISHED;
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/PojoProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/PlainTextPojoProcessor.java
similarity index 64%
rename from 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/PojoProcessor.java
rename to 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/PlainTextPojoProcessor.java
index 334b7f6..2e27bcd 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/PojoProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/PlainTextPojoProcessor.java
@@ -12,20 +12,43 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.rest.processors;
 
+import static org.apache.juneau.internal.StringUtils.*;
+
 import java.io.*;
 
 import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.util.*;
+import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.response.*;
 
 /**
- * Response handler for plain-old Java objects.
+ * Response handler for plain-old Java objects when a serializer match is not 
found and they're asking for plain/text or anything.
  */
-public final class PojoProcessor implements ResponseProcessor {
+public final class PlainTextPojoProcessor implements ResponseProcessor {
 
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException, NotAcceptable, 
BasicHttpException {
+               RestRequest req = call.getRestRequest();
+               RestResponse res = call.getRestResponse();
+               String accept = req.getHeader("Accept").orElse("*/*");
+
+               if (res.getSerializerMatch().isPresent())
+                       return NEXT;
+
+               if (! (isEmpty(accept) || accept.startsWith("text/plain") || 
accept.contains("*/*")))
+                       return NEXT;
+
+               Object o = res.getOutput(Object.class);
+
+               if (isEmpty(res.getContentType()))
+                       res.setHeader(ContentType.TEXT_PLAIN);
+
+               FinishablePrintWriter w = res.getNegotiatedWriter();
+               
w.append(req.getBeanSession().getClassMetaForObject(o).toString(o));
+               w.flush();
+               w.finish();
 
-               return 0;
+               return FINISHED;
        }
 }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ReaderProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ReaderProcessor.java
index 42a5225..06b756a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ReaderProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ReaderProcessor.java
@@ -33,14 +33,16 @@ public final class ReaderProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().isChildOf(Reader.class))
-                       return 0;
-
                RestResponse res = call.getRestResponse();
-               try (Reader r = res.getOutput(Reader.class); Writer w = 
res.getNegotiatedWriter()) {
-                       pipe(r, w);
+               Reader r = res.getOutput(Reader.class);
+
+               if (r == null)
+                       return NEXT;
+
+               try (Reader r2 = r; Writer w = res.getNegotiatedWriter()) {
+                       pipe(r2, w);
                }
-               return 1;
+               return FINISHED;
        }
 }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ResponseBeanProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ResponseBeanProcessor.java
index 9990656..09d0b93 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ResponseBeanProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ResponseBeanProcessor.java
@@ -12,10 +12,21 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.rest.processors;
 
+import static org.apache.juneau.internal.StringUtils.*;
+
 import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
 
 import org.apache.juneau.rest.*;
+import org.apache.http.*;
+import org.apache.http.Header;
 import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.http.header.*;
+import org.apache.juneau.http.part.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.httppart.bean.*;
 
 /**
  * Response handler for {@link Response @Response}-annotated objects.
@@ -25,10 +36,112 @@ public final class ResponseBeanProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().hasAnnotation(Response.class))
-                       return 0;
+               RestRequest req = call.getRestRequest();
+               RestResponse res = call.getRestResponse();
+               Object output = res.getOutput(Object.class);
+
+               if (output == null)
+                       return NEXT;
+
+               if (! (output.getClass().getAnnotation(Response.class) != null 
|| res.getResponseBeanMeta() != null))
+                       return NEXT;
+
+               ResponseBeanMeta rm = res.getResponseBeanMeta();
+               if (rm == null)
+                       rm = req.getOpContext().getResponseBeanMeta(output);
+
+               HttpPartSerializerSession ss = req.getPartSerializerSession();
+
+               ResponseBeanPropertyMeta stm = rm.getStatusMethod();
+               if (stm != null) {
+                       try {
+                               
res.setStatus((int)stm.getGetter().invoke(output));
+                       } catch (Exception e) {
+                               throw new InternalServerError(e, "Could not get 
status.");
+                       }
+               } else if (rm.getCode() != 0) {
+                       res.setStatus(rm.getCode());
+               }
 
-               return 0;
+               for (ResponseBeanPropertyMeta hm : rm.getHeaderMethods()) {
+                       String n = hm.getPartName().orElse(null);
+                       try {
+                               Object o = hm.getGetter().invoke(output);
+                               HttpPartSchema ps = hm.getSchema();
+                               if ("*".equals(n)) {
+                                       for (Object o2 : iterate(o)) {
+                                               Header h = null;
+                                               if (o2 instanceof Map.Entry) {
+                                                       
@SuppressWarnings("rawtypes")
+                                                       Map.Entry x = 
(Map.Entry)o2;
+                                                       String k = 
stringify(x.getKey());
+                                                       h = new 
SerializedHeader(k, x.getValue(), hm.getSerializerSession().orElse(ss), 
ps.getProperty(k), true);
+                                               } else if (o2 instanceof 
SerializedHeader) {
+                                                       SerializedHeader x = 
((SerializedHeader)o2);
+                                                       h = x.copyWith(ss, 
ps.getProperty(x.getName()));
+                                               } else if (o2 instanceof 
SerializedPart) {
+                                                       SerializedPart x = 
((SerializedPart)o2);
+                                                       h = 
BasicHeader.of(x.copyWith(ss, ps.getProperty(x.getName())));
+                                               } else if (o2 instanceof 
Header) {
+                                                       h = (Header)o2;
+                                               } else if (o2 instanceof 
NameValuePair) {
+                                                       h = 
BasicHeader.of((NameValuePair)o2);
+                                               } else {
+                                                       throw new 
InternalServerError("Invalid type ''{0}'' for header ''{1}''", o2 == null ? 
null : o2.getClass().getName(), n);
+                                               }
+                                               res.addHeader(h);
+                                       }
+                               } else {
+                                       Header h = null;
+                                       if (o instanceof SerializedHeader)
+                                               h = 
((SerializedHeader)o).copyWith(ss, ps);
+                                       else if (o instanceof SerializedPart)
+                                               h = 
BasicHeader.of(((SerializedPart)o).copyWith(ss, ps));
+                                       else if (o instanceof Header)
+                                               h = (Header)o;
+                                       else if (o instanceof NameValuePair)
+                                               h = 
BasicHeader.of((NameValuePair)o);
+                                       else
+                                               h = new SerializedHeader(n, o, 
hm.getSerializerSession().orElse(ss), ps, true);
+                                       res.addHeader(h);
+                               }
+                       } catch (Exception e) {
+                               throw new InternalServerError(e, "Could not set 
header ''{0}''", n);
+                       }
+               }
+
+               ResponseBeanPropertyMeta bm = rm.getBodyMethod();
+
+               if (bm != null) {
+                       Method m = bm.getGetter();
+                       try {
+                               Class<?>[] pt = m.getParameterTypes();
+                               if (pt.length == 1) {
+                                       Class<?> ptt = pt[0];
+                                       if (ptt == OutputStream.class)
+                                               m.invoke(output, 
res.getOutputStream());
+                                       else if (ptt == Writer.class)
+                                               m.invoke(output, 
res.getWriter());
+                                       return 1;
+                               }
+                               res.setOutput(m.invoke(output));
+                       } catch (Exception e) {
+                               throw new InternalServerError(e, "Could not get 
body.");
+                       }
+               }
+
+               return NEXT;  // Let PojoProcessor serialize it.
        }
-}
 
+       private Iterable<?> iterate(Object o) {
+               if (o == null)
+                       return Collections.emptyList();
+               if (o instanceof Map)
+                       return ((Map<?,?>)o).entrySet();
+               if (o.getClass().isArray())
+                       return Arrays.asList((Object[])o);
+               if (o instanceof Collection)
+                       return (Collection<?>)o;
+               throw new InternalServerError("Could not iterate over Headers 
of type ''{0}''", o.getClass().getName());
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/SerializedPojoProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/SerializedPojoProcessor.java
new file mode 100644
index 0000000..0a2863b
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/SerializedPojoProcessor.java
@@ -0,0 +1,110 @@
+// 
***************************************************************************************************************************
+// * 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.processors;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.util.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.http.header.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Response handler for plain-old Java objects.
+ */
+public final class SerializedPojoProcessor implements ResponseProcessor {
+
+       @Override /* ResponseProcessor */
+       public int process(RestCall call) throws IOException, NotAcceptable, 
BasicHttpException {
+               RestRequest req = call.getRestRequest();
+               RestResponse res = call.getRestResponse();
+               SerializerMatch sm = res.getSerializerMatch().orElse(null);
+               HttpPartSchema schema = res.getBodySchema().orElse(null);
+
+               Object o = res.getOutput(Object.class);
+
+               if (sm != null) {
+                       try {
+                               Serializer s = sm.getSerializer();
+                               MediaType mediaType = res.getMediaType();
+                               if (mediaType == null)
+                                       mediaType = sm.getMediaType();
+
+                               MediaType responseType = 
s.getResponseContentType();
+                               if (responseType == null)
+                                       responseType = mediaType;
+
+                               if (req.isPlainText())
+                                       res.setHeader(ContentType.TEXT_PLAIN);
+                               else
+                                       
res.setHeader(ContentType.of(responseType.toString()));
+
+                               SerializerSession session = s.createSession(
+                                       SerializerSessionArgs
+                                               .create()
+                                               
.properties(req.getAttributes().asMap())
+                                               
.javaMethod(req.getOpContext().getJavaMethod())
+                                               .locale(req.getLocale())
+                                               
.timeZone(req.getTimeZone().orElse(null))
+                                               .mediaType(mediaType)
+                                               .streamCharset(res.getCharset())
+                                               .schema(schema)
+                                               .debug(req.isDebug() ? true : 
null)
+                                               .uriContext(req.getUriContext())
+                                               
.useWhitespace(req.isPlainText() ? true : null)
+                                               
.resolver(req.getVarResolverSession())
+                               );
+
+                               for (Map.Entry<String,String> h : 
session.getResponseHeaders().entrySet())
+                                       res.addHeader(h.getKey(), h.getValue());
+
+                               if (! session.isWriterSerializer()) {
+                                       if (req.isPlainText()) {
+                                               FinishablePrintWriter w = 
res.getNegotiatedWriter();
+                                               ByteArrayOutputStream baos = 
new ByteArrayOutputStream();
+                                               session.serialize(o, baos);
+                                               
w.write(StringUtils.toSpacedHex(baos.toByteArray()));
+                                               w.flush();
+                                               w.finish();
+                                       } else {
+                                               FinishableServletOutputStream 
os = res.getNegotiatedOutputStream();
+                                               session.serialize(o, os);
+                                               os.flush();
+                                               os.finish();
+                                       }
+                               } else {
+                                       FinishablePrintWriter w = 
res.getNegotiatedWriter();
+                                       session.serialize(o, w);
+                                       w.flush();
+                                       w.finish();
+                               }
+                       } catch (SerializeException e) {
+                               throw new InternalServerError(e);
+                       }
+                       return FINISHED;
+               }
+
+               if (o == null)
+                       return FINISHED;
+
+               throw new NotAcceptable(
+                       "Unsupported media-type in request header ''Accept'': 
''{0}''\n\tSupported media-types: {1}",
+                       req.getHeader("Accept").orElse(""), 
res.getOpContext().getSerializers().getSupportedMediaTypes()
+               );
+       }
+}
+
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ThrowableProcessor.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ThrowableProcessor.java
index 235babe..5f74193 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ThrowableProcessor.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processors/ThrowableProcessor.java
@@ -19,7 +19,7 @@ import org.apache.juneau.rest.*;
 
 /**
  * Response handler for {@link Throwable} objects.
- * 
+ *
  * <p>
  * Adds a <c>Thrown</c> header to the response and returns <c>0</c> so that 
the processor chain can continue.
  */
@@ -28,12 +28,15 @@ public final class ThrowableProcessor implements 
ResponseProcessor {
        @Override /* ResponseProcessor */
        public int process(RestCall call) throws IOException {
 
-               if (! call.getOutputInfo().isChildOf(Throwable.class))
-                       return 0;
+               RestResponse res = call.getRestResponse();
+               Throwable t = res.getOutput(Throwable.class);
 
-               
call.addResponseHeader(Thrown.of(call.getRestResponse().getOutput(Throwable.class)));
+               if (t == null)
+                       return NEXT;
 
-               return 0; // Continue processing.
+               res.addHeader(Thrown.of(t));
+
+               return NEXT; // Continue processing as bean.
        }
 }
 

Reply via email to