You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2016/08/01 17:30:17 UTC
[28/53] [partial] incubator-juneau git commit: Merge changes from
GitHub repo.
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestResponse.java
----------------------------------------------------------------------
diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestResponse.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestResponse.java
new file mode 100755
index 0000000..8983868
--- /dev/null
+++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestResponse.java
@@ -0,0 +1,431 @@
+/***************************************************************************************************************************
+ * 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.server;
+
+import java.io.*;
+import java.util.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.jena.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.xml.*;
+
+/**
+ * Represents an HTTP response for a REST resource.
+ * <p>
+ * Essentially an extended {@link HttpServletResponse} with some special convenience methods
+ * that allow you to easily output POJOs as responses.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ * <ja>@RestMethod</ja>(name=<js>"GET"</js>)
+ * <jk>public void</jk> doGet(RestRequest req, RestResponse res) {
+ * res.setProperty(HtmlSerializerContext.<jsf>HTMLDOC_title</jsf>, <js>"My title"</js>)
+ * .setOutput(<js>"Simple string response"</js>);
+ * }
+ * </p>
+ * <p>
+ * Refer to <a class='doclink' href='package-summary.html#TOC'>REST Servlet API</a> for information about using this class.
+ * </p>
+ *
+ * @author jbognar
+ */
+public final class RestResponse extends HttpServletResponseWrapper {
+
+ private final RestRequest request;
+ 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 ObjectMap properties; // Response properties
+ SerializerGroup serializerGroup;
+ UrlEncodingSerializer urlEncodingSerializer; // The serializer used to convert arguments passed into Redirect objects.
+ private EncoderGroup encoders;
+ private RestServlet servlet;
+ private ServletOutputStream os;
+
+ /**
+ * Constructor.
+ */
+ RestResponse(RestServlet servlet, RestRequest req, HttpServletResponse res) {
+ super(res);
+ this.request = req;
+ this.servlet = servlet;
+
+ for (Map.Entry<String,Object> e : servlet.getDefaultResponseHeaders().entrySet())
+ setHeader(e.getKey(), e.getValue().toString());
+
+ try {
+ String passThroughHeaders = req.getHeader("x-response-headers");
+ if (passThroughHeaders != null) {
+ ObjectMap m = servlet.getUrlEncodingParser().parseParameter(passThroughHeaders, ObjectMap.class);
+ for (Map.Entry<String,Object> e : m.entrySet())
+ setHeader(e.getKey(), e.getValue().toString());
+ }
+ } catch (Exception e1) {
+ throw new RestException(SC_BAD_REQUEST, "Invalid format for header 'x-response-headers'. Must be in URL-encoded format.").initCause(e1);
+ }
+ }
+
+ /*
+ * Called from RestServlet after a match has been made but before the guard or method invocation.
+ */
+ @SuppressWarnings("hiding")
+ final void init(ObjectMap properties, String defaultCharset, SerializerGroup mSerializers, UrlEncodingSerializer mUrlEncodingSerializer, EncoderGroup encoders) {
+ this.properties = properties;
+ this.serializerGroup = mSerializers;
+ this.urlEncodingSerializer = mUrlEncodingSerializer;
+ this.encoders = encoders;
+
+ // Find acceptable charset
+ String h = request.getHeader("accept-charset");
+ String charset = null;
+ if (h == null)
+ charset = defaultCharset;
+ else for (MediaRange r : MediaRange.parse(h)) {
+ if (r.getQValue() > 0) {
+ if (r.getType().equals("*"))
+ charset = defaultCharset;
+ else if (RestServlet.availableCharsets.containsKey(r.getType()))
+ charset = r.getType();
+ if (charset != null)
+ break;
+ }
+ }
+
+ if (charset == null)
+ throw new RestException(SC_NOT_ACCEPTABLE, "No supported charsets in header ''Accept-Charset'': ''{0}''", request.getHeader("Accept-Charset"));
+ super.setCharacterEncoding(charset);
+ }
+
+ /**
+ * Gets the serializer group for the response.
+ *
+ * @return The serializer group for the response.
+ */
+ public SerializerGroup getSerializerGroup() {
+ return serializerGroup;
+ }
+
+ /**
+ * Returns the media types that are valid for <code>Accept</code> headers on the request.
+ *
+ * @return The set of media types registered in the parser group of this request.
+ */
+ public List<String> getSupportedMediaTypes() {
+ return serializerGroup.getSupportedMediaTypes();
+ }
+
+ /**
+ * Returns the codings that are valid for <code>Accept-Encoding</code> and <code>Content-Encoding</code> headers on the request.
+ *
+ * @return The set of media types registered in the parser group of this request.
+ * @throws RestServletException
+ */
+ public List<String> getSupportedEncodings() throws RestServletException {
+ return servlet.getEncoders().getSupportedEncodings();
+ }
+
+ /**
+ * Sets the HTTP output on the response.
+ * <p>
+ * Calling this method is functionally equivalent to returning the object in the REST Java method.
+ * <p>
+ * Can be of any of the following types:
+ * <ul>
+ * <li> {@link InputStream}
+ * <li> {@link Reader}
+ * <li> Any serializable type defined in <a href='../core/package-summary.html#PojoCategories'>POJO Categories</a>
+ * </ul>
+ * <p>
+ * If it's an {@link InputStream} or {@link Reader}, you must also specify the <code>Content-Type</code> using the {@link #setContentType(String)} method.
+ *
+ * @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;
+ }
+
+ /**
+ * Add a serializer property to send to the serializers to override a default value.
+ * <p>
+ * Can be any value specified in the following classes:
+ * <ul>
+ * <li>{@link SerializerContext}
+ * <li>{@link JsonSerializerContext}
+ * <li>{@link XmlSerializerContext}
+ * <li>{@link RdfSerializerContext}
+ * </ul>
+ *
+ * @param key The setting name.
+ * @param value The setting value.
+ * @return This object (for method chaining).
+ */
+ public RestResponse setProperty(String key, Object value) {
+ properties.put(key, value);
+ return this;
+ }
+
+ /**
+ * Returns the properties set via {@link #setProperty(String, Object)}.
+ *
+ * @return A map of all the property values set.
+ */
+ public ObjectMap getProperties() {
+ return properties;
+ }
+
+ /**
+ * Shortcut method that allows you to use varargs to simplify setting array output.
+ *
+ * <dl>
+ * <dt>Example:</dt>
+ * <dd>
+ * <p class='bcode'>
+ * <jc>// Instead of...</jc>
+ * response.setOutput(<jk>new</jk> Object[]{x,y,z});
+ *
+ * <jc>// ...call this...</jc>
+ * response.setOutput(x,y,z);
+ * </p>
+ * </dd>
+ * </dl>
+ *
+ * @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 <jk>true</jk> if this response has any output associated with it.
+ *
+ * @return <jk>true</jk> if {@code setInput()} has been called.
+ */
+ 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 <code>Accept-Encoding</code> header.
+ *
+ * @return A negotiated output stream.
+ * @throws IOException
+ */
+ public ServletOutputStream getNegotiatedOutputStream() throws IOException {
+ if (os == null) {
+ Encoder encoder = null;
+
+ String ae = request.getHeader("Accept-Encoding");
+ if (! (ae == null || ae.isEmpty())) {
+ String match = encoders != null ? encoders.findMatch(ae) : null;
+ 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 RestException(SC_NOT_ACCEPTABLE,
+ "Unsupported encoding in request header ''Accept-Encoding'': ''{0}''\n\tSupported codings: {1}",
+ ae, encoders.getSupportedEncodings()
+ );
+ }
+ } else {
+ encoder = encoders.getEncoder(match);
+
+ // Some clients don't recognize identity as an encoding, so don't set it.
+ if (! match.equals("identity"))
+ setHeader("content-encoding", match);
+ }
+ }
+ os = getOutputStream();
+ if (encoder != null) {
+ final OutputStream os2 = encoder.getOutputStream(os);
+ os = new ServletOutputStream(){
+ @Override /* OutputStream */
+ public final void write(byte[] b, int off, int len) throws IOException {
+ os2.write(b, off, len);
+ }
+ @Override /* OutputStream */
+ public final void write(int b) throws IOException {
+ os2.write(b);
+ }
+ @Override /* OutputStream */
+ public final void flush() throws IOException {
+ os2.flush();
+ }
+ @Override /* OutputStream */
+ public final void close() throws IOException {
+ os2.close();
+ }
+ };
+ }
+ }
+ return os;
+ }
+
+ @Override /* ServletResponse */
+ public ServletOutputStream getOutputStream() throws IOException {
+ if (os == null)
+ os = super.getOutputStream();
+ return os;
+ }
+
+ /**
+ * Returns <jk>true</jk> if {@link #getOutputStream()} has been called.
+ *
+ * @return <jk>true</jk> if {@link #getOutputStream()} has been called.
+ */
+ public boolean getOutputStreamCalled() {
+ return os != 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 <js>"x-content-type-options=nosniff"</js> so that output is rendered
+ * immediately on IE and Chrome without any buffering for content-type sniffing.
+ *
+ * @param contentType The value to set as the <code>Content-Type</code> 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 <code>Accept-Encoding</code> header and sets the <code>Content-Encoding</code>
+ * header to the appropriate value.
+ *
+ * @return The negotiated writer.
+ * @throws IOException
+ */
+ public PrintWriter getNegotiatedWriter() throws IOException {
+ return getWriter(false);
+ }
+
+ private PrintWriter getWriter(boolean raw) throws IOException {
+ // If plain text requested, override it now.
+ if (request.isPlainText()) {
+ setHeader("Content-Type", "text/plain");
+ }
+
+ try {
+ OutputStream out = (raw ? getOutputStream() : getNegotiatedOutputStream());
+ return new PrintWriter(new OutputStreamWriter(out, getCharacterEncoding()));
+ } catch (UnsupportedEncodingException e) {
+ String ce = getCharacterEncoding();
+ setCharacterEncoding("UTF-8");
+ throw new RestException(SC_NOT_ACCEPTABLE, "Unsupported charset in request header ''Accept-Charset'': ''{0}''", ce);
+ }
+ }
+
+ /**
+ * Returns the <code>Content-Type</code> header stripped of the charset attribute if present.
+ *
+ * @return The <code>media-type</code> portion of the <code>Content-Type</code> header.
+ */
+ public String getMediaType() {
+ String contentType = getContentType();
+ if (contentType == null)
+ return null;
+ int i = contentType.indexOf(';');
+ if (i == -1)
+ return contentType;
+ return contentType.substring(0, i).trim();
+
+ }
+
+ /**
+ * Redirects to the specified URI.
+ * <p>
+ * 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);
+ }
+
+ /**
+ * Returns the URL-encoding serializer associated with this response.
+ *
+ * @return The URL-encoding serializer associated with this response.
+ */
+ public UrlEncodingSerializer getUrlEncodingSerializer() {
+ return urlEncodingSerializer;
+ }
+
+ @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);
+ }
+}