All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.rest.RestResponse Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.rest;

import static org.apache.juneau.internal.StringUtils.*;

import java.io.*;
import java.nio.charset.*;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.juneau.*;
import org.apache.juneau.encoders.*;
import org.apache.juneau.http.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.httppart.bean.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.exception.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.serializer.*;

/**
 * Represents an HTTP response for a REST resource.
 *
 * 

* Essentially an extended {@link HttpServletResponse} with some special convenience methods that allow you to easily * output POJOs as responses. * *

* Since this class extends {@link HttpServletResponse}, developers are free to use these convenience methods, or * revert to using lower level methods like any other servlet response. * *

Example:
*

* @RestMethod(name=GET) * public void doGet(RestRequest req, RestResponse res) { * res.setOutput("Simple string response"); * } *

* *
See Also:
*
    *
*/ public final class RestResponse extends HttpServletResponseWrapper { private final RestRequest request; private RestJavaMethod restJavaMethod; private Object output; // The POJO being sent to the output. private boolean isNullOutput; // The output is null (as opposed to not being set at all) private RequestProperties properties; // Response properties private ServletOutputStream sos; private FinishableServletOutputStream os; private FinishablePrintWriter w; private HtmlDocBuilder htmlDocBuilder; private ResponseBeanMeta responseMeta; /** * Constructor. */ RestResponse(RestContext context, RestRequest req, HttpServletResponse res) throws BadRequest { super(res); this.request = req; for (Map.Entry e : context.getDefaultResponseHeaders().entrySet()) setHeader(e.getKey(), asString(e.getValue())); try { String passThroughHeaders = req.getHeader("x-response-headers"); if (passThroughHeaders != null) { HttpPartParser p = context.getPartParser(); ObjectMap m = p.createPartSession(req.getParserSessionArgs()).parse(HttpPartType.HEADER, null, passThroughHeaders, context.getBeanContext().getClassMeta(ObjectMap.class)); for (Map.Entry e : m.entrySet()) setHeader(e.getKey(), e.getValue().toString()); } } catch (Exception e1) { throw new BadRequest(e1, "Invalid format for header 'x-response-headers'. Must be in URL-encoded format."); } } /* * Called from RestServlet after a match has been made but before the guard or method invocation. */ final void init(RestJavaMethod rjm, RequestProperties properties) throws NotAcceptable { this.restJavaMethod = rjm; this.properties = properties; // Find acceptable charset String h = request.getHeader("accept-charset"); String charset = null; if (h == null) charset = rjm.defaultCharset; else for (MediaTypeRange r : MediaTypeRange.parse(h)) { if (r.getQValue() > 0) { MediaType mt = r.getMediaType(); if (mt.getType().equals("*")) charset = rjm.defaultCharset; else if (Charset.isSupported(mt.getType())) charset = mt.getType(); if (charset != null) break; } } if (charset == null) throw new NotAcceptable("No supported charsets in header ''Accept-Charset'': ''{0}''", request.getHeader("Accept-Charset")); super.setCharacterEncoding(charset); this.responseMeta = rjm.responseMeta; } /** * Gets the serializer group for the response. * *
See Also:
*
    *
* * @return The serializer group for the response. */ public SerializerGroup getSerializers() { return restJavaMethod == null ? SerializerGroup.EMPTY : restJavaMethod.serializers; } /** * Returns the media types that are valid for Accept headers on the request. * * @return The set of media types registered in the parser group of this request. */ public List getSupportedMediaTypes() { return restJavaMethod == null ? Collections.emptyList() : restJavaMethod.supportedAcceptTypes; } /** * Returns the codings that are valid for Accept-Encoding and Content-Encoding headers on * the request. * * @return The set of media types registered in the parser group of this request. * @throws RestServletException */ public List getSupportedEncodings() throws RestServletException { return restJavaMethod == null ? Collections.emptyList() : restJavaMethod.encoders.getSupportedEncodings(); } /** * Sets the HTTP output on the response. * *

* The object type can be anything allowed by the registered response handlers. * *

* Calling this method is functionally equivalent to returning the object in the REST Java method. * *

Example:
*

* @RestMethod(..., path="/example2/{personId}") * public void doGet2(RestResponse res, @Path UUID personId) { * Person p = getPersonById(personId); * res.setOutput(p); * } *

* *
Notes:
*
    *
  • * Calling this method with a null value is NOT the same as not calling this method at all. *
    A null output value means we want to serialize null as a response (e.g. as a JSON null). *
    Not calling this method or returning a value means you're handing the response yourself via the underlying stream or writer. *
    This distinction affects the {@link #hasOutput()} method behavior. *
* *
See Also:
*
    *
  • {@link RestContext#REST_responseHandlers} *
* * @param output The output to serialize to the connection. * @return This object (for method chaining). */ public RestResponse setOutput(Object output) { this.output = output; this.isNullOutput = output == null; return this; } /** * Returns a programmatic interface for setting properties for the HTML doc view. * *

* This is the programmatic equivalent to the {@link RestMethod#htmldoc() @RestMethod(htmldoc)} annotation. * *

Example:
*

* // Declarative approach. * @RestMethod( * htmldoc=@HtmlDoc( * header={ * "<p>This is my REST interface</p>" * }, * aside={ * "<p>Custom aside content</p>" * } * ) * ) * public Object doGet(RestResponse res) { * * // Equivalent programmatic approach. * res.getHtmlDocBuilder() * .header("<p>This is my REST interface</p>") * .aside("<p>Custom aside content</p>"); * } *

* *
See Also:
*
    *
  • {@link RestMethod#htmldoc()} *
* * @return A new programmatic interface for setting properties for the HTML doc view. */ public HtmlDocBuilder getHtmlDocBuilder() { if (htmlDocBuilder == null) htmlDocBuilder = new HtmlDocBuilder(properties); return htmlDocBuilder; } /** * Retrieve the properties active for this request. * *

* This contains all resource and method level properties from the following: *

    *
  • {@link RestResource#properties()} *
  • {@link RestMethod#properties()} *
  • {@link RestContextBuilder#set(String, Object)} *
* *

* The returned object is modifiable and allows you to override session-level properties before * they get passed to the serializers. *
However, properties are open-ended, and can be used for any purpose. * *

Example:
*

* @RestMethod( * properties={ * @Property(name=SERIALIZER_sortMaps, value="false") * } * ) * public Map doGet(RestResponse res, @Query("sortMaps") Boolean sortMaps) { * * // Override value if specified through query parameter. * if (sortMaps != null) * res.getProperties().put(SERIALIZER_sortMaps, sortMaps); * * return getMyMap(); * } *

* *
See Also:
*
    *
  • {@link #prop(String, Object)} *
* * @return The properties active for this request. */ public RequestProperties getProperties() { return properties; } /** * Shortcut for calling getProperties().append(name, value); fluently. * * @param name The property name. * @param value The property value. * @return This object (for method chaining). */ public RestResponse prop(String name, Object value) { this.properties.append(name, value); return this; } /** * Shortcut method that allows you to use var-args to simplify setting array output. * *
Example:
*

* // Instead of... * response.setOutput(new Object[]{x,y,z}); * * // ...call this... * response.setOutput(x,y,z); *

* * @param output The output to serialize to the connection. * @return This object (for method chaining). */ public RestResponse setOutputs(Object...output) { this.output = output; return this; } /** * Returns the output that was set by calling {@link #setOutput(Object)}. * * @return The output object. */ public Object getOutput() { return output; } /** * Returns true if this response has any output associated with it. * * @return true if {@link #setOutput(Object)} has been called, even if the value passed was null. */ public boolean hasOutput() { return output != null || isNullOutput; } /** * Sets the output to a plain-text message regardless of the content type. * * @param text The output text to send. * @return This object (for method chaining). * @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 */ public FinishableServletOutputStream getNegotiatedOutputStream() throws NotAcceptable, IOException { if (os == null) { Encoder encoder = null; EncoderGroup encoders = restJavaMethod == null ? EncoderGroup.DEFAULT : restJavaMethod.encoders; String ae = request.getHeader("Accept-Encoding"); 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, 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; } @Override /* ServletResponse */ public ServletOutputStream getOutputStream() throws IOException { if (sos == null) sos = super.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 body. * *

* 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). */ @Override /* ServletResponse */ public PrintWriter getWriter() throws IOException { return getWriter(true); } /** * 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 */ public PrintWriter getDirectWriter(String contentType) throws IOException { setContentType(contentType); setHeader("x-content-type-options", "nosniff"); return getWriter(); } /** * 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 */ public FinishablePrintWriter getNegotiatedWriter() throws NotAcceptable, IOException { return getWriter(false); } @SuppressWarnings("resource") private FinishablePrintWriter getWriter(boolean raw) 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()); 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.forString(getContentType()); } /** * 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. */ @Override /* ServletResponse */ 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; super.sendRedirect(uri); } @Override /* ServletResponse */ 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")) super.setContentType(value); else super.setHeader(name, value); } /** * Same as {@link #setHeader(String, String)} but header is defined as a response part * * @param h Header to set. * @throws SchemaValidationException * @throws SerializeException */ public void setHeader(HttpPart h) throws SchemaValidationException, SerializeException { setHeader(h.getName(), h.asString()); } /** * Returns the metadata about this response. * * @return * The metadata about this response. * Never null. */ public ResponseBeanMeta getResponseMeta() { return responseMeta; } /** * Sets metadata about this response. * * @param rbm The metadata about this response. * @return This object (for method chaining). */ public RestResponse setResponseMeta(ResponseBeanMeta rbm) { this.responseMeta = 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 isOutputType(Class c) { return c.isInstance(output); } /** * Returns this value cast to the specified class. * * @param c The class to cast to. * @return This value cast to the specified class. */ @SuppressWarnings("unchecked") public T getOutput(Class c) { return (T)output; } @Override /* ServletResponse */ public void flushBuffer() throws IOException { if (w != null) w.flush(); if (os != null) os.flush(); super.flushBuffer(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy