getContent() {
return content;
}
/**
* Returns true if the response contains output.
*
*
* This implies {@link #setContent(Object)} has been called on this object.
*
*
* Note that this also returns true even if {@link #setContent(Object)} was called with a null
* value as this means the response contains an output value of null as opposed to no value at all.
*
* @return true if the response contains output.
*/
public boolean hasContent() {
return content != null;
}
/**
* Sets the output to a plain-text message regardless of the content type.
*
* @param text The output text to send.
* @return This object.
* @throws IOException If a problem occurred trying to write to the writer.
*/
public RestResponse sendPlainText(String text) throws IOException {
setContentType("text/plain");
getNegotiatedWriter().write(text);
return this;
}
/**
* Equivalent to {@link HttpServletResponse#getOutputStream()}, except wraps the output stream if an {@link Encoder}
* was found that matched the Accept-Encoding header.
*
* @return A negotiated output stream.
* @throws NotAcceptable If unsupported Accept-Encoding value specified.
* @throws IOException Thrown by underlying stream.
*/
public FinishableServletOutputStream getNegotiatedOutputStream() throws NotAcceptable, IOException {
if (os == null) {
Encoder encoder = null;
EncoderSet encoders = request.getOpContext().getEncoders();
String ae = request.getHeaderParam("Accept-Encoding").orElse(null);
if (! (ae == null || ae.isEmpty())) {
EncoderMatch match = encoders.getEncoderMatch(ae);
if (match == null) {
// Identity should always match unless "identity;q=0" or "*;q=0" is specified.
if (ae.matches(".*(identity|\\*)\\s*;\\s*q\\s*=\\s*(0(?!\\.)|0\\.0).*")) {
throw new NotAcceptable(
"Unsupported encoding in request header ''Accept-Encoding'': ''{0}''\n\tSupported codings: {1}",
ae, Json5.of(encoders.getSupportedEncodings())
);
}
} else {
encoder = match.getEncoder();
String encoding = match.getEncoding().toString();
// Some clients don't recognize identity as an encoding, so don't set it.
if (! encoding.equals("identity"))
setHeader("content-encoding", encoding);
}
}
@SuppressWarnings("resource")
ServletOutputStream sos = getOutputStream();
os = new FinishableServletOutputStream(encoder == null ? sos : encoder.getOutputStream(sos));
}
return os;
}
/**
* Returns a ServletOutputStream suitable for writing binary data in the response.
*
*
* The servlet container does not encode the binary data.
*
*
* Calling flush() on the ServletOutputStream commits the response.
* Either this method or getWriter may be called to write the content, not both, except when reset has been called.
*
* @return The stream.
* @throws IOException If stream could not be accessed.
*/
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (sos == null)
sos = inner.getOutputStream();
return sos;
}
/**
* Returns true if {@link #getOutputStream()} has been called.
*
* @return true if {@link #getOutputStream()} has been called.
*/
public boolean getOutputStreamCalled() {
return sos != null;
}
/**
* Returns the writer to the response content.
*
*
* This methods bypasses any specified encoders and returns a regular unbuffered writer.
* Use the {@link #getNegotiatedWriter()} method if you want to use the matched encoder (if any).
*
* @return The writer.
* @throws IOException If writer could not be accessed.
*/
@Override
public PrintWriter getWriter() throws IOException {
return getWriter(true, false);
}
/**
* Convenience method meant to be used when rendering directly to a browser with no buffering.
*
*
* Sets the header "x-content-type-options=nosniff" so that output is rendered immediately on IE and Chrome
* without any buffering for content-type sniffing.
*
*
* This can be useful if you want to render a streaming 'console' on a web page.
*
* @param contentType The value to set as the Content-Type on the response.
* @return The raw writer.
* @throws IOException Thrown by underlying stream.
*/
public PrintWriter getDirectWriter(String contentType) throws IOException {
setContentType(contentType);
setHeader("X-Content-Type-Options", "nosniff");
setHeader("Content-Encoding", "identity");
return getWriter(true, true);
}
/**
* Equivalent to {@link HttpServletResponse#getWriter()}, except wraps the output stream if an {@link Encoder} was
* found that matched the Accept-Encoding header and sets the Content-Encoding
* header to the appropriate value.
*
* @return The negotiated writer.
* @throws NotAcceptable If unsupported charset in request header Accept-Charset.
* @throws IOException Thrown by underlying stream.
*/
public FinishablePrintWriter getNegotiatedWriter() throws NotAcceptable, IOException {
return getWriter(false, false);
}
@SuppressWarnings("resource")
private FinishablePrintWriter getWriter(boolean raw, boolean autoflush) throws NotAcceptable, IOException {
if (w != null)
return w;
// If plain text requested, override it now.
if (request.isPlainText())
setHeader("Content-Type", "text/plain");
try {
OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream());
w = new FinishablePrintWriter(out, getCharacterEncoding(), autoflush);
return w;
} catch (UnsupportedEncodingException e) {
String ce = getCharacterEncoding();
setCharacterEncoding("UTF-8");
throw new NotAcceptable("Unsupported charset in request header ''Accept-Charset'': ''{0}''", ce);
}
}
/**
* Returns the Content-Type header stripped of the charset attribute if present.
*
* @return The media-type portion of the Content-Type header.
*/
public MediaType getMediaType() {
return MediaType.of(getContentType());
}
/**
* Wrapper around {@link #getCharacterEncoding()} that converts the value to a {@link Charset}.
*
* @return The request character encoding converted to a {@link Charset}.
*/
public Charset getCharset() {
String s = getCharacterEncoding();
return s == null ? null : Charset.forName(s);
}
/**
* Redirects to the specified URI.
*
*
* Relative URIs are always interpreted as relative to the context root.
* This is similar to how WAS handles redirect requests, and is different from how Tomcat handles redirect requests.
*
* @param uri The redirection URL.
* @throws IOException If an input or output exception occurs
*/
@Override
public void sendRedirect(String uri) throws IOException {
char c = (uri.length() > 0 ? uri.charAt(0) : 0);
if (c != '/' && uri.indexOf("://") == -1)
uri = request.getContextPath() + '/' + uri;
inner.sendRedirect(uri);
}
/**
* Sets a response header with the given name and value.
*
*
* If the header had already been set, the new value overwrites the previous one.
*
*
* The {@link #containsHeader(String)} method can be used to test for the presence of a header before setting its value.
*
* @param name The header name.
* @param value The header value.
*/
@Override
public void setHeader(String name, String value) {
// Jetty doesn't set the content type correctly if set through this method.
// Tomcat/WAS does.
if (name.equalsIgnoreCase("Content-Type")) {
inner.setContentType(value);
ContentType ct = contentType(value);
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);
}
}
/**
* Sets a header on the request.
*
* @param name The header name.
* @param value The header value.
*
* Can be any POJO.
* Converted to a string using the specified part serializer.
*
* @return This object.
* @throws SchemaValidationException Header failed schema validation.
* @throws SerializeException Header could not be serialized.
*/
public RestResponse setHeader(String name, Object value) throws SchemaValidationException, SerializeException {
setHeader(name, request.getPartSerializerSession().serialize(HEADER, null, value));
return this;
}
/**
* Sets a header on the request.
*
* @param schema
* The schema to use to serialize the header, or null to use the default schema.
* @param name The header name.
* @param value The header value.
*
* Can be any POJO.
* Converted to a string using the specified part serializer.
*
* @return This object.
* @throws SchemaValidationException Header failed schema validation.
* @throws SerializeException Header could not be serialized.
*/
public RestResponse setHeader(HttpPartSchema schema, String name, Object value) throws SchemaValidationException, SerializeException {
setHeader(name, request.getPartSerializerSession().serialize(HEADER, schema, value));
return this;
}
/**
* Specifies the schema for the response content.
*
*
* Used by schema-aware serializers such as {@link OpenApiSerializer}. Ignored by other serializers.
*
* @param schema The content schema
* @return This object.
*/
public RestResponse setContentSchema(HttpPartSchema schema) {
this.contentSchema = optional(schema);
return this;
}
/**
* Sets the "Exception" attribute to the specified throwable.
*
*
* This exception is used by {@link CallLogger} for logging purposes.
*
* @param t The attribute value.
* @return This object.
*/
public RestResponse setException(Throwable t) {
request.setException(t);
return this;
}
/**
* Sets the "NoTrace" attribute to the specified boolean.
*
*
* This flag is used by {@link CallLogger} and tells it not to log the current request.
*
* @param b The attribute value.
* @return This object.
*/
public RestResponse setNoTrace(Boolean b) {
request.setNoTrace(b);
return this;
}
/**
* Shortcut for calling setNoTrace(true ) .
*
* @return This object.
*/
public RestResponse setNoTrace() {
return setNoTrace(true);
}
/**
* Sets the "Debug" attribute to the specified boolean.
*
*
* This flag is used by {@link CallLogger} to help determine how a request should be logged.
*
* @param b The attribute value.
* @return This object.
* @throws IOException If bodies could not be cached.
*/
public RestResponse setDebug(Boolean b) throws IOException {
request.setDebug(b);
if (b)
inner = CachingHttpServletResponse.wrap(inner);
return this;
}
/**
* Shortcut for calling setDebug(true ) .
*
* @return This object.
* @throws IOException If bodies could not be cached.
*/
public RestResponse setDebug() throws IOException {
return setDebug(true);
}
/**
* Returns the metadata about this response.
*
* @return
* The metadata about this response.
* Never null .
*/
public ResponseBeanMeta getResponseBeanMeta() {
return responseBeanMeta;
}
/**
* Sets metadata about this response.
*
* @param rbm The metadata about this response.
* @return This object.
*/
public RestResponse setResponseBeanMeta(ResponseBeanMeta rbm) {
this.responseBeanMeta = rbm;
return this;
}
/**
* Returns true if this response object is of the specified type.
*
* @param c The type to check against.
* @return true if this response object is of the specified type.
*/
public boolean isContentOfType(Class> c) {
return c.isInstance(getRawOutput());
}
/**
* Returns this value cast to the specified class.
*
* @param The class to cast to.
* @param c The class to cast to.
* @return This value cast to the specified class, or null if the object doesn't exist or isn't the specified type.
*/
@SuppressWarnings("unchecked")
public T getContent(Class c) {
if (isContentOfType(c))
return (T)getRawOutput();
return null;
}
/**
* Returns the wrapped servlet request.
*
* @return The wrapped servlet request.
*/
public HttpServletResponse getHttpServletResponse() {
return inner;
}
/**
* Forces any content in the buffer to be written to the client.
*
*
* A call to this method automatically commits the response, meaning the status code and headers will be written.
*
* @throws IOException If an I/O error occurred.
*/
@Override
public void flushBuffer() throws IOException {
if (w != null)
w.flush();
if (os != null)
os.flush();
inner.flushBuffer();
}
private Object getRawOutput() {
return content == null ? null : content.orElse(null);
}
/**
* Enabled safe-header mode.
*
*
* When enabled, invalid characters such as CTRL characters will be stripped from header values
* before they get set.
*
* @return This object.
*/
public RestResponse setSafeHeaders() {
this.safeHeaders = true;
return this;
}
/**
* Specifies the maximum length for header values.
*
*
* Header values that exceed this length will get truncated.
*
* @param value The new value for this setting. The default is 8096 .
* @return This object.
*/
public RestResponse setMaxHeaderLength(int value) {
this.maxHeaderLength = value;
return this;
}
/**
* Adds a response header with the given name and value.
*
*
* This method allows response headers to have multiple values.
*
*
* A no-op of either the name or value is null .
*
*
* Note that per RFC2616 ,
* 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.
*/
@Override
public void addHeader(String name, String 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);
}
}
}
/**
* Sets a response header.
*
*
* Any previous header values are removed.
*
*
* Value is added at the end of the headers.
*
* @param header The header.
* @return This object.
*/
public RestResponse setHeader(Header header) {
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);
String v = x.getValue();
if (v != null && v.indexOf("://") != -1)
v = resolveUris(v);
setHeader(x.getName(), v);
} else {
setHeader(header.getName(), header.getValue());
}
return this;
}
/**
* Adds a response header.
*
*
* Any previous header values are preserved.
*
*
* Value is added at the end of the headers.
*
*
* If the header is a {@link BasicUriHeader}, the URI will be resolved using the {@link RestRequest#getUriResolver()} object.
*
*
* 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.
*
*
* Note that per RFC2616 ,
* 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.
*/
public RestResponse addHeader(Header header) {
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);
}
/**
* Returns the matching serializer and media type for this response.
*
* @return The matching serializer, never null .
*/
public Optional getSerializerMatch() {
if (serializerMatch != null)
return serializerMatch;
if (serializer != null) {
serializerMatch = optional(new SerializerMatch(getMediaType(), serializer));
} else {
serializerMatch = optional(opContext.getSerializers().getSerializerMatch(request.getHeaderParam("Accept").orElse("*/*")));
}
return serializerMatch;
}
/**
* Returns the schema of the response content.
*
* @return The schema of the response content, never null .
*/
public Optional getContentSchema() {
if (contentSchema != null)
return contentSchema;
if (responseBeanMeta != null)
contentSchema = optional(responseBeanMeta.getSchema());
else {
ResponseBeanMeta rbm = opContext.getResponseBeanMeta(getContent(Object.class));
if (rbm != null)
contentSchema = optional(rbm.getSchema());
else
contentSchema = empty();
}
return contentSchema;
}
}