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 2017/09/08 23:25:37 UTC

svn commit: r21540 [15/27] - in /release/incubator/juneau: juneau-rest-client/ juneau-rest-client/.settings/ juneau-rest-client/bin/ juneau-rest-client/src/ juneau-rest-client/src/main/ juneau-rest-client/src/main/java/ juneau-rest-client/src/main/java...

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,1217 @@
+// ***************************************************************************************************************************
+// * 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 java.util.Collections.*;
+import static java.util.logging.Level.*;
+import static javax.servlet.http.HttpServletResponse.*;
+import static org.apache.juneau.internal.IOUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.nio.charset.*;
+import java.text.*;
+import java.util.*;
+import java.util.logging.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.dto.swagger.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.html.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.ini.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.parser.ParseException;
+import org.apache.juneau.rest.widget.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.uon.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Represents an HTTP request for a REST resource.
+ *
+ * <p>
+ * Equivalent to {@link HttpServletRequest} except with some additional convenience methods.
+ *
+ * <p>
+ * For reference, given the URL <js>"http://localhost:9080/contextRoot/servletPath/foo?bar=baz#qux"</js>, the
+ * following methods return the following values....
+ * <table class='styled'>
+ * 	<tr><th>Method</th><th>Value</th></tr>
+ * 	<tr><td>{@code getContextPath()}</td><td>{@code /contextRoot}</td></tr>
+ * 	<tr><td>{@code getPathInfo()}</td><td>{@code /foo}</td></tr>
+ * 	<tr><td>{@code getPathTranslated()}</td><td>{@code path-to-deployed-war-on-filesystem/foo}</td></tr>
+ * 	<tr><td>{@code getQueryString()}</td><td>{@code bar=baz}</td></tr>
+ * 	<tr><td>{@code getRequestURI()}</td><td>{@code /contextRoot/servletPath/foo}</td></tr>
+ * 	<tr><td>{@code getRequestURL()}</td><td>{@code http://localhost:9080/contextRoot/servletPath/foo}</td></tr>
+ * 	<tr><td>{@code getServletPath()}</td><td>{@code /servletPath}</td></tr>
+ * </table>
+ *
+ * <p>
+ * Refer to <a class="doclink" href="package-summary.html#TOC">REST Servlet API</a> for information about using this
+ * class.
+ */
+@SuppressWarnings("unchecked")
+public final class RestRequest extends HttpServletRequestWrapper {
+
+	private final RestContext context;
+
+	private final String method, stylesheet;
+	private RequestBody body;
+	private Method javaMethod;
+	private ObjectMap properties;
+	private SerializerGroup serializerGroup;
+	private ParserGroup parserGroup;
+	private final boolean debug;
+	private UrlEncodingParser urlEncodingParser;   // The parser used to parse URL attributes and parameters (beanContext also used to parse headers)
+	private BeanSession beanSession;
+	private VarResolverSession varSession;
+	private final RequestQuery queryParams;
+	private RequestFormData formData;
+	private Map<String,String> defFormData;
+	private RequestPathMatch pathParams;
+	private boolean isPost;
+	private UriContext uriContext;
+	private String charset, defaultCharset;
+	private RequestHeaders headers;
+	private ConfigFile cf;
+	private Swagger swagger, fileSwagger;
+	private Map<String,Widget> widgets;
+
+	/**
+	 * Constructor.
+	 */
+	RestRequest(RestContext context, HttpServletRequest req) throws ServletException {
+		super(req);
+		this.context = context;
+
+		try {
+			isPost = req.getMethod().equalsIgnoreCase("POST");
+
+			// If this is a POST, we want to parse the query parameters ourselves to prevent
+			// the servlet code from processing the HTTP body as URL-Encoded parameters.
+			queryParams = new RequestQuery();
+			if (isPost)
+				context.getUrlEncodingParser().parseIntoSimpleMap(getQueryString(), queryParams);
+			else
+				queryParams.putAll(req.getParameterMap());
+
+
+			// Get the HTTP method.
+			// Can be overridden through a "method" GET attribute.
+			String _method = super.getMethod();
+
+			String m = getQuery().getString("method");
+			if (context.allowMethodParam(m))
+				_method = m;
+
+			method = _method;
+
+			String _stylesheet = getQuery().getString("stylesheet");
+			if (_stylesheet != null)
+				req.getSession().setAttribute("stylesheet", _stylesheet);
+			stylesheet = (String)req.getSession().getAttribute("stylesheet");
+
+			headers = new RequestHeaders();
+			for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) {
+				String name = e.nextElement();
+				headers.put(name, super.getHeaders(name));
+			}
+
+			body = new RequestBody(this);
+
+			if (context.isAllowBodyParam()) {
+				String b = getQuery().getString("body");
+				if (b != null) {
+					headers.put("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
+					body.load(b.getBytes(UTF8));
+				}
+			}
+
+			if (context.isAllowHeaderParams())
+				headers.setQueryParams(queryParams);
+
+			debug = "true".equals(getQuery().getString("debug", "false")) || "true".equals(getHeaders().getString("Debug", "false"));
+
+			this.pathParams = new RequestPathMatch();
+
+		} catch (RestException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new ServletException(e);
+		}
+	}
+
+	/*
+	 * Called from RestServlet after a match has been made but before the guard or method invocation.
+	 */
+	@SuppressWarnings("hiding")
+	final void init(Method javaMethod, ObjectMap properties, Map<String,String> defHeader,
+			Map<String,String> defQuery, Map<String,String> defFormData, String defaultCharset,
+			SerializerGroup mSerializers, ParserGroup mParsers, UrlEncodingParser mUrlEncodingParser,
+			BeanContext beanContext, EncoderGroup encoders, Map<String,Widget> widgets) {
+		this.javaMethod = javaMethod;
+		this.properties = properties;
+		this.urlEncodingParser = mUrlEncodingParser;
+		this.beanSession = beanContext.createSession();
+		this.pathParams
+			.setParser(urlEncodingParser)
+			.setBeanSession(beanSession);
+		this.queryParams
+			.addDefault(defQuery)
+			.setParser(urlEncodingParser)
+			.setBeanSession(beanSession);
+		this.headers
+			.addDefault(defHeader)
+			.addDefault(context.getDefaultRequestHeaders())
+			.setParser(urlEncodingParser)
+			.setBeanSession(beanSession);
+		this.body
+			.setEncoders(encoders)
+			.setParsers(mParsers)
+			.setHeaders(headers)
+			.setBeanSession(beanSession)
+			.setUrlEncodingParser(mUrlEncodingParser);
+		this.serializerGroup = mSerializers;
+		this.parserGroup = mParsers;
+		this.defaultCharset = defaultCharset;
+		this.defFormData = defFormData;
+		this.widgets = widgets;
+
+		if (debug) {
+			String msg = ""
+				+ "\n=== HTTP Request (incoming) ===================================================="
+				+ toString()
+				+ "\n=== END ========================================================================";
+			context.getLogger().log(Level.WARNING, msg);
+		}
+
+		if (isPlainText())
+			this.properties.put(SerializerContext.SERIALIZER_useWhitespace, true);
+	}
+
+	/**
+	 * Returns a string of the form <js>"HTTP method-name full-url"</js>
+	 *
+	 * @return A description of the request.
+	 */
+	public String getDescription() {
+		String qs = getQueryString();
+		return "HTTP " + getMethod() + " " + getRequestURI() + (qs == null ? "" : "?" + qs);
+	}
+
+	/**
+	 * Same as {@link #getAttribute(String)} but returns a default value if not found.
+	 *
+	 * @param name The request attribute name.
+	 * @param def The default value if the attribute doesn't exist.
+	 * @return The request attribute value.
+	 */
+	public Object getAttribute(String name, Object def) {
+		Object o = super.getAttribute(name);
+		return (o == null ? def : o);
+	}
+
+	/**
+	 * Shorthand method for calling {@link #setAttribute(String, Object)} fluently.
+	 *
+	 * @param name The request attribute name.
+	 * @param value The request attribute value.
+	 * @return This object (for method chaining).
+	 */
+	public RestRequest attr(String name, Object value) {
+		setAttribute(name, value);
+		return this;
+	}
+
+
+	/**
+	 * Resolves the specified property.
+	 *
+	 * @param cm
+	 * 	The <code>CallMethod</code> object where the <code>HtmlDocSerializer</code> settings are defined.
+	 * 	Optional value.  If not specified, then won't resolve <code>HtmlDocSerializer</code> properties.
+	 * @param category
+	 * 	The property category.
+	 * 	The possible values are:
+	 * 	<ul>
+	 * 		<li>
+	 * 			<js>"Attribute"</js> - Value returned by {@link HttpServletRequest#getAttribute(String)}.
+	 * 		<li>
+	 * 			<js>"FormData"</js> - Value returned by {@link RestRequest#getFormData(String)}.
+	 * 		<li>
+	 * 			<js>"Header"</js> - Value returned by {@link RestRequest#getHeader(String)}.
+	 * 		<li>
+	 * 			<js>"HtmlDocSerializer"</js>
+	 * 			<br>Valid names:
+	 * 			<ul>
+	 * 				<li><js>"aside"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_aside}
+	 * 				<li><js>"footer"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_footer}
+	 * 				<li><js>"header"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_header}
+	 * 				<li><js>"links.list"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_links}
+	 * 				<li><js>"nav"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_nav}
+	 * 				<li><js>"noResultsMessage"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_noResultsMessage}
+	 * 				<li><js>"nowrap"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_nowrap}
+	 * 				<li><js>"script.list"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_script}
+	 * 				<li><js>"style.list"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_style}
+	 * 				<li><js>"stylesheet"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_stylesheet}
+	 * 				<li><js>"template"</js> - See {@link HtmlDocSerializerContext#HTMLDOC_template}
+	 * 			</ul>
+	 * 		<li>
+	 * 			<js>"Path"</js> - Value returned by {@link RestRequest#getPath(String)}.
+	 * 		<li>
+	 * 			<js>"Query"</js> = Value returned by {@link RestRequest#getQuery(String)}.
+	 * 		<li>
+	 * 			<js>"Request"</js>
+	 * 			<br>Valid names:
+	 * 			<ul>
+	 * 				<li><js>"contextPath"</js> - Value returned by {@link RestRequest#getContextPath()}
+	 * 				<li><js>"method"</js> - Value returned by {@link RestRequest#getMethod()}
+	 * 				<li><js>"methodDescription"</js> - Value returned by {@link RestRequest#getMethodDescription()}
+	 * 				<li><js>"methodSummary"</js> - Value returned by {@link RestRequest#getMethodSummary()}
+	 * 				<li><js>"pathInfo"</js> - Value returned by {@link RestRequest#getPathInfo()}
+	 * 				<li><js>"requestParentURI"</js> - Value returned by {@link UriContext#getRootRelativePathInfoParent()}
+	 * 				<li><js>"requestURI"</js> - Value returned by {@link RestRequest#getRequestURI()}
+	 * 				<li><js>"servletClass"</js> - The class name of the servlet
+	 * 				<li><js>"servletClassSimple"</js> - The simple class name of the servlet.
+	 * 				<li><js>"servletDescription"</js> - Value returned by {@link RestRequest#getServletDescription()}
+	 * 				<li><js>"servletParentURI"</js> - Value returned by {@link UriContext#getRootRelativeServletPathParent()}
+	 * 				<li><js>"servletPath"</js> - See {@link RestRequest#getServletPath()}
+	 * 				<li><js>"servletTitle"</js> - See {@link RestRequest#getServletTitle()}
+	 * 				<li><js>"servletURI"</js> - See {@link UriContext#getRootRelativeServletPath()}
+	 * 				<li><js>"siteName"</js> - See {@link RestRequest#getSiteName()}
+	 * 			</ul>
+	 * 	</ul>
+	 * @param name The property name.
+	 * @return The resolve property, or <jk>null</jk> if it wasn't found.
+	 */
+	public Object resolveProperty(CallMethod cm, String category, String name) {
+		char c = category.charAt(0);
+		if (c == 'A') {
+			if ("Attribute".equals(category))
+				return getAttribute(name);
+		} else if (c == 'F') {
+			if ("FormData".equals(category))
+				return getFormData(name);
+		} else if (c == 'H') {
+			if ("Header".equals(category))
+				return getHeader(name);
+			if ("HtmlDocSerializer".equals(category) && cm != null) {
+				char c2 = StringUtils.charAt(name, 0);
+				if (c2 == 'a') {
+					if ("aside".equals(name))
+						return cm.htmlAside == null ? null : resolveVars(cm.htmlAside);
+				} else if (c2 == 'f') {
+					if ("footer".equals(name))
+						return cm.htmlFooter == null ? null : resolveVars(cm.htmlFooter);
+				} else if (c2 == 'h') {
+					if ("header".equals(name))
+						return cm.htmlHeader == null ? null : resolveVars(cm.htmlHeader);
+				} else if (c2 == 'l') {
+					if ("links.list".equals(name)) {
+						if (cm.htmlLinks == null || cm.htmlLinks.length == 0)
+							return null;
+						try {
+							List<String> la = new ArrayList<String>();
+							for (String l : cm.htmlLinks) {
+								// Temporary backwards compatibility with JSON object format.
+								if (l.startsWith("{")) {
+									ObjectMap m = new ObjectMap(l);
+									for (Map.Entry<String,Object> e : m.entrySet())
+										la.add(resolveVars(e.getKey()) + ":" + resolveVars(StringUtils.toString(e.getValue())));
+								} else {
+									la.add(resolveVars(l));
+								}
+							}
+							return la;
+						} catch (ParseException e) {
+							throw new RuntimeException(e);
+						}
+					}
+				} else if (c2 == 'n') {
+					if ("nav".equals(name))
+						return cm.htmlNav == null ? null : resolveVars(cm.htmlNav);
+					if ("noResultsMessage".equals(name))
+						return cm.htmlNoResultsMessage == null ? null : resolveVars(cm.htmlNoResultsMessage);
+					if ("nowrap".equals(name))
+						return cm.htmlNoWrap;
+				} else if (c2 == 's') {
+					if ("script.list".equals(name)) {
+						Set<String> l = new LinkedHashSet<String>();
+						if (cm.htmlScript != null)
+							l.add(resolveVars(cm.htmlScript));
+						for (Widget w : getWidgets().values()) {
+							String script;
+							try {
+								script = w.getScript(this);
+							} catch (Exception e) {
+								script = e.getLocalizedMessage();
+							}
+							if (script != null)
+								l.add(resolveVars(script));
+						}
+						return l;
+					}
+					if ("style.list".equals(name)) {
+						Set<String> l = new LinkedHashSet<String>();
+						if (cm.htmlStyle != null)
+							l.add(resolveVars(cm.htmlStyle));
+						for (Widget w : getWidgets().values()) {
+							String style;
+							try {
+								style = w.getStyle(this);
+							} catch (Exception e) {
+								style = e.getLocalizedMessage();
+							}
+							if (style != null)
+								l.add(resolveVars(style));
+						}
+						return l;
+					}
+					if ("stylesheet".equals(name)) {
+						String s = getStylesheet();
+						// Exclude absolute URIs to stylesheets for security reasons.
+						if (s == null || isAbsoluteUri(s))
+							s = cm.htmlStylesheet;
+						return s == null ? null : resolveVars(s);
+					}
+				} else if (c2 == 't') {
+					if ("template".equals(name))
+						return cm.htmlTemplate;
+				}
+			}
+		} else if (c == 'P') {
+			if ("Path".equals(category))
+				return getPath(name);
+		} else if (c == 'Q') {
+			if ("Query".equals(category))
+				return getQuery(name);
+		} else if (c == 'R') {
+			if ("Request".equals(category)) {
+				char c2 = StringUtils.charAt(name, 0);
+				if (c2 == 'c') {
+					if ("contextPath".equals(name))
+						return getContextPath();
+				} else if (c2 == 'm') {
+					if ("method".equals(name))
+						return getMethod();
+					if ("methodDescription".equals(name))
+						return getMethodDescription();
+					if ("methodSummary".equals(name))
+						return getMethodSummary();
+				} else if (c2 == 'p') {
+					if ("pathInfo".equals(name))
+						return getPathInfo();
+				} else if (c2 == 'r') {
+					if ("requestParentURI".equals(name))
+						return getUriContext().getRootRelativePathInfoParent();
+					if ("requestURI".equals(name))
+						return getRequestURI();
+				} else if (c2 == 's') {
+					if ("servletClass".equals(name))
+						return getContext().getResource().getClass().getName();
+					if ("servletClassSimple".equals(name))
+						return getContext().getResource().getClass().getSimpleName();
+					if ("servletDescription".equals(name))
+						return getServletDescription();
+					if ("servletParentURI".equals(name))
+						return getUriContext().getRootRelativeServletPathParent();
+					if ("servletPath".equals(name))
+						return getServletPath();
+					if ("servletTitle".equals(name))
+						return getServletTitle();
+					if ("servletURI".equals(name))
+						return getUriContext().getRootRelativeServletPath();
+					if ("siteName".equals(name))
+						return getSiteName();
+				}
+			}
+		}
+		return null;
+	}
+
+	//--------------------------------------------------------------------------------
+	// Properties
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Servlet calls this method to initialize the properties.
+	 */
+	RestRequest setProperties(ObjectMap properties) {
+		this.properties = properties;
+		return this;
+	}
+
+	/**
+	 * Retrieve the properties active for this request.
+	 *
+	 * <p>
+	 * These properties can be modified by the request.
+	 *
+	 * @return The properties active for this request.
+	 */
+	public ObjectMap getProperties() {
+		return this.properties;
+	}
+
+	/**
+	 * Shortcut for calling <code>getProperties().append(name, value);</code> fluently.
+	 *
+	 * @param name The property name.
+	 * @param value The property value.
+	 * @return This object (for method chaining).
+	 */
+	public RestRequest prop(String name, Object value) {
+		this.properties.append(name, value);
+		return this;
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Headers
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Returns the headers on this request.
+	 *
+	 * @return The headers on this request.  Never <jk>null</jk>.
+	 */
+	public RequestHeaders getHeaders() {
+		return headers;
+	}
+
+	@Override /* ServletRequest */
+	public String getHeader(String name) {
+		return getHeaders().getString(name);
+	}
+
+	@Override /* ServletRequest */
+	public Enumeration<String> getHeaders(String name) {
+		String[] v = headers.get(name);
+		if (v == null || v.length == 0)
+			return Collections.enumeration(Collections.EMPTY_LIST);
+		return Collections.enumeration(Arrays.asList(v));
+	}
+
+	/**
+	 * Returns the media types that are valid for <code>Content-Type</code> headers on the request.
+	 *
+	 * @return The set of media types registered in the parser group of this request.
+	 */
+	public List<MediaType> getSupportedMediaTypes() {
+		return parserGroup.getSupportedMediaTypes();
+	}
+
+	/**
+	 * Sets the charset to expect on the request body.
+	 */
+	@Override /* ServletRequest */
+	public void setCharacterEncoding(String charset) {
+		this.charset = charset;
+	}
+
+	/**
+	 * Returns the charset specified on the <code>Content-Type</code> header, or <js>"UTF-8"</js> if not specified.
+	 */
+	@Override /* ServletRequest */
+	public String getCharacterEncoding() {
+		if (charset == null) {
+			// Determine charset
+			// NOTE:  Don't use super.getCharacterEncoding() because the spec is implemented inconsistently.
+			// Jetty returns the default charset instead of null if the character is not specified on the request.
+			String h = getHeader("Content-Type");
+			if (h != null) {
+				int i = h.indexOf(";charset=");
+				if (i > 0)
+					charset = h.substring(i+9).trim();
+			}
+			if (charset == null)
+				charset = defaultCharset;
+			if (! Charset.isSupported(charset))
+				throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported charset in header ''Content-Type'': ''{0}''", h);
+		}
+		return charset;
+	}
+
+	@Override /* ServletRequest */
+	public Locale getLocale() {
+		String h = headers.getString("Accept-Language");
+		if (h != null) {
+			MediaTypeRange[] mr = MediaTypeRange.parse(h);
+			if (mr.length > 0)
+				return toLocale(mr[0].getMediaType().getType());
+		}
+		return super.getLocale();
+	}
+
+	@Override /* ServletRequest */
+	public Enumeration<Locale> getLocales() {
+		String h = headers.getString("Accept-Language");
+		if (h != null) {
+			MediaTypeRange[] mr = MediaTypeRange.parse(h);
+			if (mr.length > 0) {
+				List<Locale> l = new ArrayList<Locale>(mr.length);
+				for (MediaTypeRange r : mr)
+					l.add(toLocale(r.getMediaType().getType()));
+				return enumeration(l);
+			}
+		}
+		return super.getLocales();
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Query parameters
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Equivalent to {@link #getParameterMap()}, but only looks for query parameters in the URL, not form posts.
+	 *
+	 * <p>
+	 * This method can be used to retrieve query parameters without triggering the underlying servlet API to load and
+	 * parse the request body.
+	 *
+	 * <p>
+	 * This object is modifiable.
+	 *
+	 * @return The query parameters as a modifiable map.
+	 */
+	public RequestQuery getQuery() {
+		return queryParams;
+	}
+
+	/**
+	 * Shortcut for calling <code>getQuery().getString(name)</code>.
+	 *
+	 * @param name The query parameter name.
+	 * @return The query parameter value, or <jk>null<jk> if not found.
+	 */
+	public String getQuery(String name) {
+		return getQuery().getString(name);
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Form data parameters
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Retrieves the URL-encoded form data from the request if the body has already been cached locally.
+	 *
+	 * @return The URL-encoded form data from the request.
+	 */
+	public RequestFormData getFormData() {
+		try {
+			if (formData == null) {
+				formData = new RequestFormData();
+				formData.setParser(urlEncodingParser).setBeanSession(beanSession);
+				if (! body.isLoaded()) {
+					formData.putAll(getParameterMap());
+				} else {
+					Map<String,String> m = urlEncodingParser.parse(body.getReader(), Map.class, String.class, String.class);
+					for (Map.Entry<String,String> e : m.entrySet()) {
+						formData.put(e.getKey(), e.getValue());
+					}
+				}
+			}
+			formData.addDefault(defFormData);
+			return formData;
+		} catch (Exception e) {
+			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+		}
+	}
+
+	/**
+	 * Shortcut for calling <code>getFormData().getString(name)</code>.
+	 *
+	 * @param name The form data parameter name.
+	 * @return The form data parameter value, or <jk>null<jk> if not found.
+	 */
+	public String getFormData(String name) {
+		return getFormData().getString(name);
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Path parameters
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Retrieves the URL-encoded form data from the request if the body has already been cached locally.
+	 *
+	 * @return The URL-encoded form data from the request.
+	 */
+	public RequestPathMatch getPathMatch() {
+		return pathParams;
+	}
+
+	/**
+	 * Shortcut for calling <code>getPathMatch().get(name)</code>.
+	 *
+	 * @param name The path variable name.
+	 * @return The path variable value, or <jk>null<jk> if not found.
+	 */
+	public String getPath(String name) {
+		return getPathMatch().get(name);
+	}
+
+	//--------------------------------------------------------------------------------
+	// Body methods
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Returns the body of this HTTP request.
+	 *
+	 * @return The body of this HTTP request.
+	 */
+	public RequestBody getBody() {
+		return body;
+	}
+
+	/**
+	 * Returns the HTTP body content as a {@link Reader}.
+	 *
+	 * <p>
+	 * If {@code allowHeaderParams} init parameter is true, then first looks for {@code &body=xxx} in the URL query
+	 * string.
+	 *
+	 * <p>
+	 * Automatically handles GZipped input streams.
+	 */
+	@Override /* ServletRequest */
+	public BufferedReader getReader() throws IOException {
+		return getBody().getReader();
+	}
+
+	/**
+	 * Returns the HTTP body content as an {@link InputStream}.
+	 *
+	 * <p>
+	 * Automatically handles GZipped input streams.
+	 *
+	 * @return The negotiated input stream.
+	 * @throws IOException If any error occurred while trying to get the input stream or wrap it in the GZIP wrapper.
+	 */
+	@Override /* ServletRequest */
+	public ServletInputStream getInputStream() throws IOException {
+		return getBody().getInputStream();
+	}
+
+	ServletInputStream getRawInputStream() throws IOException {
+		return super.getInputStream();
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// URI-related methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* HttpServletRequest */
+	public String getContextPath() {
+		String cp = context.getContextPath();
+		return cp == null ? super.getContextPath() : cp;
+	}
+
+	@Override /* HttpServletRequest */
+	public String getServletPath() {
+		String cp = context.getContextPath();
+		String sp = super.getServletPath();
+		return cp == null || ! sp.startsWith(cp) ? sp : sp.substring(cp.length());
+	}
+
+	/**
+	 * Returns the URI context of the request.
+	 *
+	 * <p>
+	 * The URI context contains all the information about the URI of the request, such as the servlet URI, context
+	 * path, etc...
+	 *
+	 * @return The URI context of the request.
+	 */
+	public UriContext getUriContext() {
+		if (uriContext == null) {
+			String scheme = getScheme();
+			int port = getServerPort();
+			StringBuilder authority = new StringBuilder(getScheme()).append("://").append(getServerName());
+			if (! (port == 80 && "http".equals(scheme) || port == 443 && "https".equals(scheme)))
+				authority.append(':').append(port);
+			uriContext = new UriContext(authority.toString(), getContextPath(), getServletPath(), super.getPathInfo());
+		}
+		return uriContext;
+	}
+
+	/**
+	 * Returns a URI resolver that can be used to convert URIs to absolute or root-relative form.
+	 *
+	 * @param resolution The URI resolution rule.
+	 * @param relativity The relative URI relativity rule.
+	 * @return The URI resolver for this request.
+	 */
+	public UriResolver getUriResolver(UriResolution resolution, UriRelativity relativity) {
+		return new UriResolver(resolution, relativity, getUriContext());
+	}
+
+	/**
+	 * Shortcut for calling {@link #getUriResolver()} using {@link UriResolution#ROOT_RELATIVE} and
+	 * {@link UriRelativity#RESOURCE}
+	 *
+	 * @return The URI resolver for this request.
+	 */
+	public UriResolver getUriResolver() {
+		return new UriResolver(UriResolution.ROOT_RELATIVE, UriRelativity.RESOURCE, getUriContext());
+	}
+
+	/**
+	 * Returns the URI for this request.
+	 *
+	 * <p>
+	 * Similar to {@link #getRequestURI()} but returns the value as a {@link URI}.
+	 * It also gives you the capability to override the query parameters (e.g. add new query parameters to the existing
+	 * URI).
+	 *
+	 * @param includeQuery If <jk>true</jk> include the query parameters on the request.
+	 * @param addQueryParams Augment the request URI with the specified query parameters.
+	 * @return A new URI.
+	 */
+	public URI getUri(boolean includeQuery, Map<String,?> addQueryParams) {
+		String uri = getRequestURI();
+		if (includeQuery || addQueryParams != null) {
+			StringBuilder sb = new StringBuilder(uri);
+			RequestQuery rq = this.queryParams.copy();
+			if (addQueryParams != null)
+				for (Map.Entry<String,?> e : addQueryParams.entrySet())
+					rq.put(e.getKey(), e.getValue());
+			if (! rq.isEmpty())
+				sb.append('?').append(rq.toQueryString());
+			uri = sb.toString();
+		}
+		try {
+			return new URI(uri);
+		} catch (URISyntaxException e) {
+			// Shouldn't happen.
+			throw new RuntimeException(e);
+		}
+	}
+
+	//--------------------------------------------------------------------------------
+	// Labels
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Returns the localized site name.
+	 *
+	 * <p>
+	 * The site name is intended to be a title that can be applied to the entire site.
+	 *
+	 * <p>
+	 * One possible use is if you want to add the same title to the top of all pages by defining a header on a
+	 * common parent class like so:
+	 * <p class='bcode'>
+	 * 	htmldoc=<ja>@HtmlDoc</ja>(
+	 * 		header={
+	 * 			<js>"&lt;h1&gt;$R{siteName}&lt;/h1&gt;"</js>,
+	 * 			<js>"&lt;h2&gt;$R{servletTitle}&lt;/h2&gt;"</js>
+	 * 		}
+	 * 	)
+	 * </p>
+	 *
+	 * <p>
+	 * Equivalent to calling {@link RestInfoProvider#getSiteName(RestRequest)} with this object.
+	 *
+	 * @return The localized servlet label.
+	 */
+	public String getSiteName() {
+		return context.getInfoProvider().getSiteName(this);
+	}
+
+	/**
+	 * Returns the localized servlet title.
+	 *
+	 * <p>
+	 * Equivalent to calling {@link RestInfoProvider#getTitle(RestRequest)} with this object.
+	 *
+	 * @return The localized servlet label.
+	 */
+	public String getServletTitle() {
+		return context.getInfoProvider().getTitle(this);
+	}
+
+	/**
+	 * Returns the localized servlet description.
+	 *
+	 * <p>
+	 * Equivalent to calling {@link RestInfoProvider#getDescription(RestRequest)} with this object.
+	 *
+	 * @return The localized servlet description.
+	 */
+	public String getServletDescription() {
+		return context.getInfoProvider().getDescription(this);
+	}
+
+	/**
+	 * Returns the localized method summary.
+	 *
+	 * <p>
+	 * Equivalent to calling {@link RestInfoProvider#getMethodSummary(String, RestRequest)} with this object.
+	 *
+	 * @return The localized method description.
+	 */
+	public String getMethodSummary() {
+		return context.getInfoProvider().getMethodSummary(javaMethod.getName(), this);
+	}
+
+	/**
+	 * Returns the localized method description.
+	 *
+	 * <p>
+	 * Equivalent to calling {@link RestInfoProvider#getMethodDescription(String, RestRequest)} with this object.
+	 *
+	 * @return The localized method description.
+	 */
+	public String getMethodDescription() {
+		return context.getInfoProvider().getMethodDescription(javaMethod.getName(), this);
+	}
+
+	//--------------------------------------------------------------------------------
+	// Other methods
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Returns the value of the <jk>"stylesheet"</js> parameter.
+	 *
+	 * @return The value of the <jk>"stylesheet"</js> parameter, or <jk>null</jk> if it wasn't specified.
+	 */
+	protected String getStylesheet() {
+		return stylesheet;
+	}
+
+	/**
+	 * Returns the serializers associated with this request.
+	 *
+	 * @return The serializers associated with this request.
+	 */
+	public SerializerGroup getSerializerGroup() {
+		return serializerGroup;
+	}
+
+	/**
+	 * Returns the parsers associated with this request.
+	 *
+	 * @return The parsers associated with this request.
+	 */
+	public ParserGroup getParserGroup() {
+		return parserGroup;
+	}
+
+
+	/**
+	 * Returns the method of this request.
+	 *
+	 * <p>
+	 * If <code>allowHeaderParams</code> init parameter is <jk>true</jk>, then first looks for
+	 * <code>&amp;method=xxx</code> in the URL query string.
+	 */
+	@Override /* ServletRequest */
+	public String getMethod() {
+		return method;
+	}
+
+	/**
+	 * Returns the HTTP 1.1 method name of the request as an enum.
+	 *
+	 * <p>
+	 * Note that non-RFC2616 method names resolve as {@link HttpMethod#OTHER}.
+	 *
+	 * @return The HTTP method.
+	 */
+	public HttpMethod getHttpMethod() {
+		return HttpMethod.forString(method);
+	}
+
+	@Override /* ServletRequest */
+	public int getContentLength() {
+		return getBody().getContentLength();
+	}
+
+	int getRawContentLength() {
+		return super.getContentLength();
+	}
+
+	/**
+	 * Returns <jk>true</jk> if <code>&amp;plainText=true</code> was specified as a URL parameter.
+	 *
+	 * <p>
+	 * This indicates that the <code>Content-Type</code> of the output should always be set to <js>"text/plain"</js>
+	 * to make it easy to render in a browser.
+	 *
+	 * <p>
+	 * This feature is useful for debugging.
+	 *
+	 * @return <jk>true</jk> if {@code &amp;plainText=true} was specified as a URL parameter
+	 */
+	public boolean isPlainText() {
+		return "true".equals(getQuery().getString("plainText", "false"));
+	}
+
+	/**
+	 * Shortcut method for calling {@link MessageBundle#getString(Locale, String, Object...)} based on the request locale.
+	 *
+	 * @param key The message key.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 * @return The localized message.
+	 */
+	public String getMessage(String key, Object...args) {
+		return context.getMessages().getString(getLocale(), key, args);
+	}
+
+	/**
+	 * Returns the resource bundle for the request locale.
+	 *
+	 * @return The resource bundle.  Never <jk>null</jk>.
+	 */
+	public MessageBundle getResourceBundle() {
+		return context.getMessages().getBundle(getLocale());
+	}
+
+	/**
+	 * Returns the servlet handling the request.
+	 *
+	 * <p>
+	 * Can be used to access servlet-init parameters or annotations during requests, such as in calls to
+	 * {@link RestGuard#guard(RestRequest, RestResponse)}..
+	 *
+	 * @return The servlet handling the request.
+	 */
+	public RestContext getContext() {
+		return context;
+	}
+
+	/**
+	 * Returns the java method handling the request.
+	 *
+	 * <p>
+	 * Can be used to access the method name or method annotations during requests, such as in calls to
+	 * {@link RestGuard#guard(RestRequest, RestResponse)}.
+	 *
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul>
+	 * 	<li>This returns null when evaluating servlet-level guards since the method has not been resolved at that
+	 * 		point of execution.
+	 * </ul>
+	 *
+	 * @return The Java method handling the request, or <code>null</code> if the method has not yet been resolved.
+	 */
+	public Method getJavaMethod() {
+		return javaMethod;
+	}
+
+	/**
+	 * Returns the {@link BeanSession} associated with this request.
+	 *
+	 * @return The request bean session.
+	 */
+	public BeanSession getBeanSession() {
+		return beanSession;
+	}
+
+	/**
+	 * Returns the variable resolver session for this request using session objects created by
+	 * {@link RestCallHandler#getSessionObjects(RestRequest)}.
+	 *
+	 * <p>
+	 * See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * @return The variable resolver for this request.
+	 */
+	public VarResolverSession getVarResolverSession() {
+		if (varSession == null)
+			varSession = context.getVarResolver().createSession(context.getCallHandler().getSessionObjects(this));
+		return varSession;
+	}
+
+	/**
+	 * Shortcut for calling <code>getVarResolverSession().resolve(input)</code>.
+	 *
+	 * @param input The input string to resolve variables in.
+	 * @return The string with variables resolved, or <jk>null</jk> if input is null.
+	 */
+	public String resolveVars(String input) {
+		return getVarResolverSession().resolve(input);
+	}
+
+	/**
+	 * Returns an instance of a {@link ReaderResource} that represents the contents of a resource text file from the
+	 * classpath.
+	 *
+	 * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
+	 * @param resolveVars
+	 * 	If <jk>true</jk>, any {@link org.apache.juneau.rest.annotation.Parameter} variables will be
+	 * 	resolved by the variable resolver returned by {@link #getVarResolverSession()}.
+	 * 	<br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 * @param mediaType The value to set as the <js>"Content-Type"</js> header for this object.
+	 * @return A new reader resource, or <jk>null</jk> if resource could not be found.
+	 * @throws IOException
+	 */
+	public ReaderResource getReaderResource(String name, boolean resolveVars, MediaType mediaType) throws IOException {
+		String s = context.getResourceAsString(name, getLocale());
+		if (s == null)
+			return null;
+		ReaderResource.Builder b = new ReaderResource.Builder().mediaType(mediaType).contents(s);
+		if (resolveVars)
+			b.varResolver(getVarResolverSession());
+		return b.build();
+	}
+
+	/**
+	 * Same as {@link #getReaderResource(String, boolean, MediaType)} except uses the resource mime-type map
+	 * constructed using {@link RestConfig#addMimeTypes(String...)} to determine the media type.
+	 *
+	 * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
+	 * @param resolveVars
+	 * 	If <jk>true</jk>, any {@link org.apache.juneau.rest.annotation.Parameter} variables will be
+	 * 	resolved by the variable resolver returned by {@link #getVarResolverSession()}.
+	 * 	<br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 * @return A new reader resource, or <jk>null</jk> if resource could not be found.
+	 * @throws IOException
+	 */
+	public ReaderResource getReaderResource(String name, boolean resolveVars) throws IOException {
+		return getReaderResource(name, resolveVars, MediaType.forString(context.getMediaTypeForName(name)));
+	}
+
+	/**
+	 * Same as {@link #getReaderResource(String, boolean)} with <code>resolveVars == <jk>false</jk></code>
+	 *
+	 * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
+	 * @return A new reader resource, or <jk>null</jk> if resource could not be found.
+	 * @throws IOException
+	 */
+	public ReaderResource getReaderResource(String name) throws IOException {
+		return getReaderResource(name, false, MediaType.forString(context.getMediaTypeForName(name)));
+	}
+
+	/**
+	 * Returns the config file associated with the servlet.
+	 *
+	 * @return
+	 * 	The config file associated with the servlet, or <jk>null</jk> if servlet does not have a config file
+	 * 	associated with it.
+	 */
+	public ConfigFile getConfigFile() {
+		if (cf == null)
+			cf = context.getConfigFile().getResolving(getVarResolverSession());
+		return cf;
+	}
+
+	/**
+	 * Returns the localized swagger associated with the servlet.
+	 *
+	 * @return
+	 * 	The swagger associated with the servlet.
+	 * 	Never <jk>null</jk>.
+	 */
+	public Swagger getSwagger() {
+		if (swagger == null)
+			swagger = context.getInfoProvider().getSwagger(this);
+		return swagger;
+	}
+
+	/**
+	 * Returns the widgets used for resolving <js>"$W{...}"</js> string variables.
+	 *
+	 * @return
+	 * 	The widgets used for resolving <js>"$W{...}"</js> string variables.
+	 * 	Never <jk>null</jk>.
+	 */
+	public Map<String,Widget> getWidgets() {
+		return widgets;
+	}
+
+	/**
+	 * Returns the localized Swagger from the file system.
+	 *
+	 * <p>
+	 * Looks for a file called <js>"{ServletClass}_{locale}.json"</js> in the same package as this servlet and returns
+	 * it as a parsed {@link Swagger} object.
+	 *
+	 * <p>
+	 * Returned objects are cached for later quick-lookup.
+	 *
+	 * @return The parsed swagger object, or <jk>null</jk> if the swagger file could not be found.
+	 */
+	protected Swagger getSwaggerFromFile() {
+		if (fileSwagger == null)
+			fileSwagger = context.getInfoProvider().getSwaggerFromFile(this.getLocale());
+		if (fileSwagger == null)
+			fileSwagger = Swagger.NULL;
+		return fileSwagger == Swagger.NULL ? null : fileSwagger;
+	}
+
+	@Override /* Object */
+	public String toString() {
+		StringBuilder sb = new StringBuilder("\n").append(getDescription()).append("\n");
+		sb.append("---Headers---\n");
+		for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) {
+			String h = e.nextElement();
+			sb.append("\t").append(h).append(": ").append(getHeader(h)).append("\n");
+		}
+		sb.append("---Default Servlet Headers---\n");
+		for (Map.Entry<String,String> e : context.getDefaultRequestHeaders().entrySet()) {
+			sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n");
+		}
+		if (javaMethod == null) {
+			sb.append("***init() not called yet!***\n");
+		} else if (method.equals("PUT") || method.equals("POST")) {
+			try {
+				sb.append("---Body UTF-8---\n");
+				sb.append(body.asString()).append("\n");
+				sb.append("---Body Hex---\n");
+				sb.append(body.asHex()).append("\n");
+			} catch (Exception e1) {
+				sb.append(e1.getLocalizedMessage());
+				context.getLogger().log(WARNING, e1, "Error occurred while trying to read debug input.");
+			}
+		}
+		return sb.toString();
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Utility methods
+	//--------------------------------------------------------------------------------
+
+	/*
+	 * Converts an Accept-Language value entry to a Locale.
+	 */
+	private static Locale toLocale(String lang) {
+		String country = "";
+		int i = lang.indexOf('-');
+		if (i > -1) {
+			country = lang.substring(i+1).trim();
+			lang = lang.substring(0,i).trim();
+		}
+		return new Locale(lang, country);
+	}
+
+
+	void setJavaMethod(Method method) {
+		this.javaMethod = method;
+	}
+}
\ No newline at end of file

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolver.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolver.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolver.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,53 @@
+// ***************************************************************************************************************************
+// * 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 org.apache.juneau.rest.annotation.*;
+
+/**
+ * Class used to resolve {@link Class} objects to instances.
+ *
+ * <p>
+ * Used to convert classes defined via {@link RestResource#children() @RestResource.children()} into child instances.
+ *
+ * <p>
+ * Subclasses can be created to provide customized resource resolution.
+ * These can be associated with REST resources in one of the following ways:
+ * <ul>
+ * 	<li>{@link RestResource#resourceResolver() @RestResource.resourceResolver()} annotation.
+ * 	<li>{@link RestConfig#setResourceResolver(Class)}/{@link RestConfig#setResourceResolver(RestResourceResolver)}
+ * 		methods.
+ * </ul>
+ *
+ * <p>
+ * An instance of this class can also be passed in through the servlet context as the context attribute
+ * {@link RestContext#REST_resourceResolver}.
+ */
+public interface RestResourceResolver {
+
+	/**
+	 * Resolves the specified class to a resource object.
+	 *
+	 * <p>
+	 * Subclasses can override this method to provide their own custom resolution.
+	 *
+	 * <p>
+	 * The default implementation simply creates a new class instance using {@link Class#newInstance()}.
+	 *
+	 * @param c The class to resolve.
+	 * @param config The initialization configuration for the resource.
+	 * @return The instance of that class.
+	 * @throws RestServletException If class could not be resolved.
+	 */
+	Object resolve(Class<?> c, RestConfig config) throws RestServletException;
+}

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolver.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolverSimple.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolverSimple.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolverSimple.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,59 @@
+// ***************************************************************************************************************************
+// * 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.ClassUtils.*;
+
+import java.lang.reflect.*;
+
+/**
+ * Denotes the default resolver.
+ * 
+ * <p>
+ * The default implementation simply instantiates the class using one of the following constructors:
+ * <ul>
+ * 	<li><code><jk>public</jk> T(RestConfig)</code>
+ * 	<li><code><jk>public</jk> T()</code>
+ * </ul>
+ *
+ * <p>
+ * The former constructor can be used to get access to the {@link RestConfig} object to get access to the
+ * config file and initialization information or make programmatic modifications to the resource before
+ * full initialization.
+ *
+ * <p>
+ * Non-<code>RestServlet</code> classes can also add the following two methods to get access to the
+ * {@link RestConfig} and {@link RestContext} objects:
+ * <ul>
+ * 	<li><code><jk>public void</jk> init(RestConfig);</code>
+ * 	<li><code><jk>public void</jk> init(RestContext);</code>
+ * </ul>
+ *
+ */
+public class RestResourceResolverSimple implements RestResourceResolver {
+
+	@Override /* RestResourceResolver */
+	public Object resolve(Class<?> c, RestConfig config) throws RestServletException {
+		try {
+			Constructor<?> c1 = findPublicConstructor(c, RestConfig.class);
+			if (c1 != null)
+				return c1.newInstance(config);
+			c1 = findPublicConstructor(c);
+			if (c1 != null)
+				return c1.newInstance();
+		} catch (Exception e) {
+			throw new RestServletException("Could not instantiate resource class ''{0}''", c.getName()).initCause(e);
+		}
+		throw new RestServletException("Could not find public constructor for class ''{0}''.", c);
+	}
+}
\ No newline at end of file

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResourceResolverSimple.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,803 @@
+// ***************************************************************************************************************************
+// * 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 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.html.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
+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>
+ * 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.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<ja>@RestMethod</ja>(name=<js>"GET"</js>)
+ * 	<jk>public void</jk> doGet(RestRequest req, RestResponse res) {
+ * 		res.setPageTitle(<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.
+ */
+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 ServletOutputStream os;
+	private PrintWriter w;
+
+	/**
+	 * Constructor.
+	 */
+	RestResponse(RestContext context, RestRequest req, HttpServletResponse res) {
+		super(res);
+		this.request = req;
+
+		for (Map.Entry<String,Object> e : context.getDefaultResponseHeaders().entrySet())
+			setHeader(e.getKey(), e.getValue().toString());
+
+		try {
+			String passThroughHeaders = req.getHeader("x-response-headers");
+			if (passThroughHeaders != null) {
+				PartParser p = context.getUrlEncodingParser();
+				ObjectMap m = p.parse(PartType.HEADER, passThroughHeaders, context.getBeanContext().getClassMeta(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 (MediaTypeRange r : MediaTypeRange.parse(h)) {
+			if (r.getQValue() > 0) {
+				MediaType mt = r.getMediaType();
+				if (mt.getType().equals("*"))
+					charset = defaultCharset;
+				else if (Charset.isSupported(mt.getType()))
+					charset = mt.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<MediaType> 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 encoders.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 class="doclink"
+	 * 		href="../../../../overview-summary.html#Core.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}
+	 * </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 var-args to simplify setting array output.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <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>
+	 *
+	 * @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())) {
+				EncoderMatch match = encoders != null ? encoders.getEncoderMatch(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 = 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);
+				}
+			}
+			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();
+					}
+					@Override /* ServletOutputStream */
+					public boolean isReady() {
+						return true;
+					}
+					@Override /* ServletOutputStream */
+					public void setWriteListener(WriteListener arg0) {
+						throw new NoSuchMethodError();
+					}
+				};
+			}
+		}
+		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.
+	 *
+	 * <p>
+	 * 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.
+	 *
+	 * <p>
+	 * 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 (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 PrintWriter(new OutputStreamWriter(out, getCharacterEncoding()));
+			return w;
+		} 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 MediaType getMediaType() {
+		return MediaType.forString(getContentType());
+	}
+
+	/**
+	 * 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);
+	}
+
+	/**
+	 * Sets the HTML header section contents.
+	 *
+	 * <p>
+	 * The format of this value is HTML.
+	 *
+	 * <p>
+	 * The page header normally contains the title and description, but this value can be used to override the contents
+	 * to be whatever you want.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no header.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#header() @HtmlDoc.header()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML header section contents.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlHeader(Object value) {
+		return setProperty(HtmlDocSerializerContext.HTMLDOC_header, value);
+	}
+
+	/**
+	 * Sets the links in the HTML nav section.
+	 *
+	 * <p>
+	 * The format of this value is a lax-JSON map of key/value pairs where the keys are the link text and the values are
+	 * relative (to the servlet) or absolute URLs.
+	 *
+	 * <p>
+	 * The page links are positioned immediately under the title and text.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no value.
+	 *
+	 * <p>
+	 * This field can also use URIs of any support type in {@link UriResolver}.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#links() @HtmlDoc.links()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML nav section links links.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlLinks(String[] value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_links, value);
+		return this;
+	}
+
+	/**
+	 * Sets the HTML nav section contents.
+	 *
+	 * <p>
+	 * The format of this value is HTML.
+	 *
+	 * <p>
+	 * The nav section of the page contains the links.
+	 *
+	 * <p>
+	 * The format of this value is HTML.
+	 *
+	 * <p>
+	 * When a value is specified, the {@link #setHtmlLinks(String[])} value will be ignored.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no value.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#nav() @HtmlDoc.nav()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML nav section contents.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlNav(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_nav, value);
+		return this;
+	}
+
+	/**
+	 * Sets the HTML aside section contents.
+	 *
+	 * <p>
+	 * The format of this value is HTML.
+	 *
+	 * <p>
+	 * The aside section typically floats on the right side of the page.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no value.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#aside() @HtmlDoc.aside()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML aside section contents.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to waste
+	 * 				string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlAside(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_aside, value);
+		return this;
+	}
+
+	/**
+	 * Sets the HTML footer section contents.
+	 *
+	 * <p>
+	 * The format of this value is HTML.
+	 *
+	 * <p>
+	 * The footer section typically floats on the bottom of the page.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no value.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#footer() @HtmlDoc.footer()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML footer section contents.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlFooter(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_footer, value);
+		return this;
+	}
+
+	/**
+	 * Sets the HTML CSS style section contents.
+	 *
+	 * <p>
+	 * The format of this value is CSS.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no value.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#style() @HtmlDoc.style()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML CSS style section contents.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlStyle(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_style, value);
+		return this;
+	}
+
+	/**
+	 * Sets the CSS URL in the HTML CSS style section.
+	 *
+	 * <p>
+	 * The format of this value is a comma-delimited list of URLs.
+	 *
+	 * <p>
+	 * Specifies the URL to the stylesheet to add as a link in the style tag in the header.
+	 *
+	 * <p>
+	 * The format of this value is CSS.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>) and can use URL protocols defined
+	 * by {@link UriResolver}.
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#stylesheet() @HtmlDoc.stylesheet()} annotation.
+	 *
+	 * @param value
+	 * 	The CSS URL in the HTML CSS style section.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlStylesheet(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_stylesheet, value);
+		return this;
+	}
+
+	/**
+	 * Sets the HTML script section contents.
+	 *
+	 * <p>
+	 * The format of this value is Javascript.
+	 *
+	 * <p>
+	 * This field can contain variables (e.g. <js>"$L{my.localized.variable}"</js>).
+	 * <br>See {@link RestContext#getVarResolver()} for the list of supported variables.
+	 *
+	 * <p>
+	 * A value of <js>"NONE"</js> can be used to force no value.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#script() @HtmlDoc.script()} annotation.
+	 *
+	 * @param value
+	 * 	The HTML script section contents.
+	 * 	Object will be converted to a string using {@link Object#toString()}.
+	 * 	<p>
+	 * 	<ul class='doctree'>
+	 * 		<li class='info'>
+	 * 			<b>Tip:</b>  Use {@link StringMessage} to generate value with delayed serialization so as not to
+	 * 				waste string concatenation cycles on non-HTML views.
+	 * 	</ul>
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlScript(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_script, value);
+		return this;
+	}
+
+	/**
+	 * Shorthand method for forcing the rendered HTML content to be no-wrap.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#nowrap() @HtmlDoc.nowrap()} annotation.
+	 *
+	 * @param value The new nowrap setting.
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlNoWrap(boolean value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_nowrap, value);
+		return this;
+	}
+
+	/**
+	 * Specifies the text to display when serializing an empty array or collection.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#noResultsMessage() @HtmlDoc.noResultsMessage()}
+	 * annotation.
+	 *
+	 * @param value The text to display when serializing an empty array or collection.
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlNoResultsMessage(Object value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_noResultsMessage, value);
+		return this;
+	}
+
+	/**
+	 * Specifies the template class to use for rendering the HTML page.
+	 *
+	 * <p>
+	 * By default, uses {@link HtmlDocTemplateBasic} to render the contents, although you can provide your own custom
+	 * renderer or subclasses from the basic class to have full control over how the page is rendered.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#template() @HtmlDoc.template()} annotation.
+	 *
+	 * @param value The HTML page template to use to render the HTML page.
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlTemplate(Class<? extends HtmlDocTemplate> value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_template, value);
+		return this;
+	}
+
+	/**
+	 * Specifies the template class to use for rendering the HTML page.
+	 *
+	 * <p>
+	 * By default, uses {@link HtmlDocTemplateBasic} to render the contents, although you can provide your own custom
+	 * renderer or subclasses from the basic class to have full control over how the page is rendered.
+	 *
+	 * <p>
+	 * This is the programmatic equivalent to the {@link HtmlDoc#template() @HtmlDoc.template()} annotation.
+	 *
+	 * @param value The HTML page template to use to render the HTML page.
+	 * @return This object (for method chaining).
+	 */
+	public RestResponse setHtmlTemplate(HtmlDocTemplate value) {
+		properties.put(HtmlDocSerializerContext.HTMLDOC_template, value);
+		return this;
+	}
+
+	@Override /* ServletResponse */
+	public void flushBuffer() throws IOException {
+		if (w != null)
+			w.flush();
+		if (os != null)
+			os.flush();
+		super.flushBuffer();
+	}
+}
\ No newline at end of file

Propchange: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain