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 <[email protected]>
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.
}
}