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 2018/01/06 03:39:36 UTC

[1/3] juneau git commit: RestContext refactoring.

Repository: juneau
Updated Branches:
  refs/heads/master b766e1d07 -> 96fae4f98


http://git-wip-us.apache.org/repos/asf/juneau/blob/76b634af/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
index 3d727f6..d53cfa5 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
@@ -642,7 +642,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * <b>Configuration property:</b>  REST call handler.
 	 *
 	 * <p>
-	 * Same as {@link #callHandler(Class)} but allows you to pass in a call handler instance.
+	 * Same as {@link #callHandler(Class)} except input is a pre-constructed instance.
 	 * 
 	 * @param restHandler The new call handler for this resource.
 	 * @return This object (for method chaining).
@@ -728,9 +728,9 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 
 	/**
 	 * <b>Configuration property:</b>  Children.
-	 *
+	 * 
 	 * <p>
-	 * Same as {@link #children(Class...)} but allows you to pass in already-constructed child instances.
+	 * Same as {@link #children(Class...)} except input is pre-constructed instances.
 	 * 
 	 * @param children 
 	 * 	The children to add to this resource.
@@ -766,38 +766,80 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Resource path.   
+	 * <b>Configuration property:</b>  Classpath resource finder. 
+	 * 
+	 * <p>
+	 * Used to retrieve localized files from the classpath.
+	 * 
+	 * <h6 class='topic'>Notes:</h6>
+	 * <ul class='spaced-list'>
+	 * 	<li>Property:  {@link RestContext#REST_classpathResourceFinder}
+	 * 	<li>Annotations: 
+	 * 		<ul>
+	 * 			<li>{@link RestResource#classpathResourceFinder()} 
+	 * 		</ul>
+	 * 	<li>Methods: 
+	 * 		<ul>
+	 * 			<li>{@link RestContextBuilder#classpathResourceFinder(Class)}
+	 * 			<li>{@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
+	 * 		</ul>
+	 * 	<li>
+	 * 		The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
+	 * 		resources on the classpath and JVM working directory.
+	 * 		<br>The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
+	 * 		up the parent class hierarchy.
+	 * 		<br>Each of these classes can be extended to provide customized handling of resource retrieval.
+	 * </ul>
+	 * 
+	 * @param classpathResourceFinder The resource finder class.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder classpathResourceFinder(Class<? extends ClasspathResourceFinder> classpathResourceFinder) {
+		return set(REST_classpathResourceFinder, classpathResourceFinder);
+	}
+	
+	/**
+	 * <b>Configuration property:</b>  Classpath resource finder. 
+	 * 
+	 * <p>
+	 * Same as {@link #classpathResourceFinder(ClasspathResourceFinder)} except input is a pre-constructed instance.
+	 * 
+	 * @param classpathResourceFinder The resource finder instance.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder classpathResourceFinder(ClasspathResourceFinder classpathResourceFinder) {
+		return set(REST_classpathResourceFinder, classpathResourceFinder);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Client version header.
 	 *
 	 * <p>
-	 * Identifies the URL subpath relative to the parent resource.
+	 * Specifies the name of the header used to denote the client version on HTTP requests.
 	 *
 	 * <p>
+	 * The client version is used to support backwards compatibility for breaking REST interface changes.
+	 * <br>Used in conjunction with {@link RestMethod#clientVersion()} annotation.
+	 * 
+	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_path}
+	 * 	<li>Property:  {@link RestContext#REST_clientVersionHeader}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#path()}
-	 * 		</ul> 
+	 * 			<li>{@link RestResource#clientVersionHeader()} 
+	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#path(String)} 
+	 * 			<li>{@link RestContextBuilder#clientVersionHeader(String)}
 	 * 		</ul>
-	 * 	<li>This annotation is ignored on top-level servlets (i.e. servlets defined in <code>web.xml</code> files).
-	 * 		<br>Therefore, implementers can optionally specify a path value for documentation purposes.
-	 * 	<li>Typically, this setting is only applicable to resources defined as children through the 
-	 * 		{@link RestResource#children()} annotation.
-	 * 		<br>However, it may be used in other ways (e.g. defining paths for top-level resources in microservices).
 	 *	</ul>
 	 *
-	 * @param path The URL path of this resource.
+	 * @param clientVersionHeader The name of the HTTP header that denotes the client version.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder path(String path) {
-		if (startsWith(path, '/'))
-			path = path.substring(1);
-		this.path = path;
-		return this;
+	public RestContextBuilder clientVersionHeader(String clientVersionHeader) {
+		return set(REST_clientVersionHeader, clientVersionHeader);
 	}
 
 	/**
@@ -836,58 +878,54 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Render response stack traces in responses.
+	 * <b>Configuration property:</b>  Response converters.
 	 *
 	 * <p>
-	 * Render stack traces in HTTP response bodies when errors occur.
+	 * Associates one or more {@link RestConverter converters} with a resource class.
+	 * These converters get called immediately after execution of the REST method in the same order specified in the
+	 * annotation.
+	 *
+	 * <p>
+	 * Can be used for performing post-processing on the response object before serialization.
 	 *
+	 * <p>
+	 * Default converter implementations are provided in the <a class='doclink'
+	 * href='../converters/package-summary.html#TOC'>org.apache.juneau.rest.converters</a> package.
+	 * 
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_renderResponseStackTraces}
-	 * 	<li>Annotations:
+	 * 	<li>Property:  {@link RestContext#REST_converters}
+	 * 	<li>Annotation:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#renderResponseStackTraces()}
+	 * 			<li>{@link RestResource#converters()}
+	 * 			<li>{@link RestMethod#converters()}
 	 * 		</ul>
-	 * 	<li>Methods:
+	 * 	<li>Method:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#renderResponseStackTraces(boolean)}
+	 * 			<li>{@link RestContextBuilder#converters(Class...)}
+	 * 			<li>{@link RestContextBuilder#converters(RestConverter...)}
 	 * 		</ul>
-	 * 	<li>Useful for debugging, although allowing stack traces to be rendered may cause security concerns so use
-	 * 		caution when enabling.
+	 * 	<li>{@link RestConverter} classes must have either a no-arg or {@link PropertyStore} argument constructors.
 	 *	</ul>
 	 *
-	 * @param value The new value for this setting.
+	 * @param converters The converter classes to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder renderResponseStackTraces(boolean value) {
-		return set(REST_renderResponseStackTraces, value);
+	public RestContextBuilder converters(Class<?>...converters) {
+		return addTo(REST_converters, converters);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Use stack trace hashes.
+	 * <b>Configuration property:</b>  Response converters.
 	 *
 	 * <p>
-	 * When enabled, the number of times an exception has occurred will be determined based on stack trace hashsums,
-	 * made available through the {@link RestException#getOccurrence()} method.
-	 *
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_useStackTraceHashes}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#useStackTraceHashes()} 
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#useStackTraceHashes(boolean)}
-	 * 		</ul>
-	 *	</ul>
+	 * Same as {@link #converters(Class...)} except input is pre-constructed instances.
 	 *
-	 * @param value The new value for this setting.
+	 * @param converters The converter classes to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder useStackTraceHashes(boolean value) {
-		return set(REST_useStackTraceHashes, value);
+	public RestContextBuilder converters(RestConverter...converters) {
+		return addTo(REST_converters, converters);
 	}
 
 	/**
@@ -902,8 +940,8 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * 	<li>Annotations:
 	 * 		<ul>
 	 * 			<li>{@link RestResource#defaultCharset()}
-					<li>{@link RestMethod#defaultCharset()}
-				</ul>
+	 * 			<li>{@link RestMethod#defaultCharset()}
+	 * 	</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
 	 * 			<li>{@link RestContextBuilder#defaultCharset(String)}
@@ -918,210 +956,171 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	}
 
 	/**
-	 * <b>Configuration property:</b>  The maximum allowed input size (in bytes) on HTTP requests.
+	 * <b>Configuration property:</b>  Default request headers.
+	 *
+	 * <p>
+	 * Adds class-level default HTTP request headers to this resource.
 	 *
 	 * <p>
-	 * Useful for alleviating DoS attacks by throwing an exception when too much input is received instead of resulting
-	 * in out-of-memory errors which could affect system stability.
-	 * 
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_maxInput}
+	 * 	<li>Property:  {@link RestContext#REST_defaultRequestHeaders}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#maxInput()}
-	 * 			<li>{@link RestMethod#maxInput()}
+	 * 			<li>{@link RestResource#defaultRequestHeaders()}
+	 * 			<li>{@link RestMethod#defaultRequestHeaders()} 
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#maxInput(String)}
+	 * 			<li>{@link RestContextBuilder#defaultRequestHeader(String,Object)}
+	 * 			<li>{@link RestContextBuilder#defaultRequestHeaders(String...)}
 	 * 		</ul>
-	 * 	<li>String value that gets resolved to a <jk>long</jk>.
-	 * 	<li>Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:  
-	 * 		<js>'K'</js>, <js>'M'</js>, <js>'G'</js>.
-	 * 	<li>A value of <js>"-1"</js> can be used to represent no limit.
+	 * 	<li>Strings are of the format <js>"Header-Name: header-value"</js>.
+	 * 	<li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
+	 * 	<li>Key and value is trimmed of whitespace.
+	 * 	<li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
+	 * 	<li>Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
+	 * 	<li>The most useful reason for this annotation is to provide a default <code>Accept</code> header when one is not
+	 * 		specified so that a particular default {@link Serializer} is picked.
 	 *	</ul>
 	 *
-	 * @param value The new value for this setting.
+	 * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
 	 * @return This object (for method chaining).
+	 * @throws RestServletException If malformed header is found.
 	 */
-	public RestContextBuilder maxInput(String value) {
-		return set(REST_maxInput, value);
+	public RestContextBuilder defaultRequestHeaders(String...headers) throws RestServletException {
+		for (String header : headers) {
+			String[] h = RestUtils.parseHeader(header);
+			if (h == null)
+				throw new RestServletException("Invalid default request header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
+			defaultRequestHeader(h[0], h[1]);
+		}
+		return this;
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Java method parameter resolvers.
-	 *
-	 * <p>
-	 * By default, the Juneau framework will automatically Java method parameters of various types (e.g.
-	 * <code>RestRequest</code>, <code>Accept</code>, <code>Reader</code>).
-	 * This annotation allows you to provide your own resolvers for your own class types that you want resolved.
+	 * <b>Configuration property:</b>  Default request headers.
 	 *
 	 * <p>
-	 * For example, if you want to pass in instances of <code>MySpecialObject</code> to your Java method, define
-	 * the following resolver:
-	 * <p class='bcode'>
-	 * 	<jk>public class</jk> MyRestParam <jk>extends</jk> RestParam {
+	 * Same as {@link #defaultRequestHeaders(String...)} but adds a single header name/value pair.
 	 *
-	 * 		<jc>// Must have no-arg constructor!</jc>
-	 * 		<jk>public</jk> MyRestParam() {
-	 * 			<jc>// First two parameters help with Swagger doc generation.</jc>
-	 * 			<jk>super</jk>(<jsf>QUERY</jsf>, <js>"myparam"</js>, MySpecialObject.<jk>class</jk>);
-	 * 		}
+	 * @param name The HTTP header name.
+	 * @param value The HTTP header value.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder defaultRequestHeader(String name, Object value) {
+		return addTo(REST_defaultRequestHeaders, name, value);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Default response headers.
 	 *
-	 * 		<jc>// The method that creates our object.
-	 * 		// In this case, we're taking in a query parameter and converting it to our object.</jc>
-	 * 		<jk>public</jk> Object resolve(RestRequest req, RestResponse res) <jk>throws</jk> Exception {
-	 * 			<jk>return new</jk> MySpecialObject(req.getQuery().get(<js>"myparam"</js>));
-	 * 		}
-	 * 	}
-	 * </p>
+	 * <p>
+	 * Specifies default values for response headers.
 	 *
+	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_paramResolvers}
+	 * 	<li>Property:  {@link RestContext#REST_defaultResponseHeaders}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#paramResolvers()}
+	 * 			<li>{@link RestResource#defaultResponseHeaders()} 
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#paramResolvers(Class...)}
+	 * 			<li>{@link RestContextBuilder#defaultResponseHeader(String,Object)}
+	 * 			<li>{@link RestContextBuilder#defaultResponseHeaders(String...)}
 	 * 		</ul>
-	 * 	<li>{@link RestParam} classes must have either a no-arg or {@link PropertyStore} argument constructors.
+	 * 	<li>Strings are of the format <js>"Header-Name: header-value"</js>.
+	 * 	<li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
+	 * 	<li>Key and value is trimmed of whitespace.
+	 * 	<li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
+	 * 	<li>This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of 
+	 * 		the Java methods.
+	 * 	<li>The header value will not be set if the header value has already been specified (hence the 'default' in the name).
+	 * 	<li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
+	 * 		annotation.
 	 *	</ul>
 	 *
-	 * @param paramResolvers The parameter resolvers to add to this config.
+	 * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
 	 * @return This object (for method chaining).
+	 * @throws RestServletException If malformed header is found.
 	 */
-	@SuppressWarnings("unchecked")
-	public RestContextBuilder paramResolvers(Class<? extends RestParam>...paramResolvers) {
-		return addTo(REST_paramResolvers, paramResolvers);
+	public RestContextBuilder defaultResponseHeaders(String...headers) throws RestServletException {
+		for (String header : headers) {
+			String[] h = RestUtils.parseHeader(header);
+			if (h == null)
+				throw new RestServletException("Invalid default response header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
+			defaultResponseHeader(h[0], h[1]);
+		}
+		return this;
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Java method parameter resolvers.
-	 *
-	 * <p>
-	 * By default, the Juneau framework will automatically Java method parameters of various types (e.g.
-	 * <code>RestRequest</code>, <code>Accept</code>, <code>Reader</code>).
-	 * This annotation allows you to provide your own resolvers for your own class types that you want resolved.
+	 * <b>Configuration property:</b>  Default response headers.
 	 *
 	 * <p>
-	 * For example, if you want to pass in instances of <code>MySpecialObject</code> to your Java method, define
-	 * the following resolver:
-	 * <p class='bcode'>
-	 * 	<jk>public class</jk> MyRestParam <jk>extends</jk> RestParam {
+	 * Same as {@link #defaultResponseHeaders(String...)} but adds a single header name/value pair.
 	 *
-	 * 		<jc>// Must have no-arg constructor!</jc>
-	 * 		<jk>public</jk> MyRestParam() {
-	 * 			<jc>// First two parameters help with Swagger doc generation.</jc>
-	 * 			<jk>super</jk>(<jsf>QUERY</jsf>, <js>"myparam"</js>, MySpecialObject.<jk>class</jk>);
-	 * 		}
-	 *
-	 * 		<jc>// The method that creates our object.
-	 * 		// In this case, we're taking in a query parameter and converting it to our object.</jc>
-	 * 		<jk>public</jk> Object resolve(RestRequest req, RestResponse res) <jk>throws</jk> Exception {
-	 * 			<jk>return new</jk> MySpecialObject(req.getQuery().get(<js>"myparam"</js>));
-	 * 		}
-	 * 	}
-	 * </p>
-	 *
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_paramResolvers}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#paramResolvers()}
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#paramResolvers(Class...)}
-	 * 		</ul>
-	 * 	<li>{@link RestParam} classes must have either a no-arg or {@link PropertyStore} argument constructors.
-	 *	</ul>
-	 *
-	 * @param paramResolvers The parameter resolvers to add to this config.
+	 * @param name The HTTP header name.
+	 * @param value The HTTP header value.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder paramResolvers(RestParam...paramResolvers) {
-		return addTo(REST_paramResolvers, paramResolvers);
+	public RestContextBuilder defaultResponseHeader(String name, Object value) {
+		return addTo(REST_defaultResponseHeaders, name, value);
 	}
-
+	
 	/**
-	 * <b>Configuration property:</b>  Response converters.
-	 *
-	 * <p>
-	 * Associates one or more {@link RestConverter converters} with a resource class.
-	 * These converters get called immediately after execution of the REST method in the same order specified in the
-	 * annotation.
+	 * <b>Configuration property:</b>  Compression encoders. 
 	 *
 	 * <p>
-	 * Can be used for performing post-processing on the response object before serialization.
+	 * These can be used to enable various kinds of compression (e.g. <js>"gzip"</js>) on requests and responses.
 	 *
-	 * <p>
-	 * Default converter implementations are provided in the <a class='doclink'
-	 * href='../converters/package-summary.html#TOC'>org.apache.juneau.rest.converters</a> package.
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	<jc>// Servlet with automated support for GZIP compression</jc>
+	 * 	<ja>@RestResource</ja>(encoders={GzipEncoder.<jk>class</jk>})
+	 * 	<jk>public</jk> MyRestServlet <jk>extends</jk> RestServlet {
+	 * 		...
+	 * 	}
+	 * </p>
 	 * 
-	 * <h5 class='section'>Notes:</h5>
+	 * <h6 class='topic'>Notes:</h6>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_converters}
-	 * 	<li>Annotation:
+	 * 	<li>Property:  {@link RestContext#REST_encoders}
+	 * 	<li>Annotations: 
 	 * 		<ul>
-	 * 			<li>{@link RestResource#converters()}
-	 * 			<li>{@link RestMethod#converters()}
+	 * 			<li>{@link RestResource#encoders()} 
+	 * 			<li>{@link RestMethod#encoders()} 
 	 * 		</ul>
-	 * 	<li>Method:
+	 * 	<li>Methods: 
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#converters(Class...)}
-	 * 			<li>{@link RestContextBuilder#converters(RestConverter...)}
+	 * 			<li>{@link RestContextBuilder#encoders(Class...)}
+	 * 			<li>{@link RestContextBuilder#encoders(Encoder...)}
 	 * 		</ul>
-	 * 	<li>{@link RestConverter} classes must have either a no-arg or {@link PropertyStore} argument constructors.
-	 *	</ul>
+	 * 	<li>Instance classes must provide a public no-arg constructor, or a public constructor that takes in a
+	 * 		{@link PropertyStore} object.
+	 * 	<li>Instance class can be defined as an inner class of the REST resource class.
+	 * </ul>
 	 *
-	 * @param converters The converter classes to add to this config.
+	 * @param encoders Encoder classes to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder converters(Class<?>...converters) {
-		return addTo(REST_converters, converters);
+	public RestContextBuilder encoders(Class<?>...encoders) {
+		return addTo(REST_encoders, encoders);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Response converters.
-	 *
-	 * <p>
-	 * Associates one or more {@link RestConverter converters} with a resource class.
-	 * These converters get called immediately after execution of the REST method in the same order specified in the
-	 * annotation.
-	 *
-	 * <p>
-	 * Can be used for performing post-processing on the response object before serialization.
+	 * <b>Configuration property:</b>  Compression encoders. 
 	 *
 	 * <p>
-	 * Default converter implementations are provided in the <a class='doclink'
-	 * href='../converters/package-summary.html#TOC'>org.apache.juneau.rest.converters</a> package.
-	 * 
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_converters}
-	 * 	<li>Annotation:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#converters()}
-	 * 			<li>{@link RestMethod#converters()}
-	 * 		</ul>
-	 * 	<li>Method:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#converters(Class...)}
-	 * 			<li>{@link RestContextBuilder#converters(RestConverter...)}
-	 * 		</ul>
-	 * 	<li>{@link RestConverter} classes must have either a no-arg or {@link PropertyStore} argument constructors.
-	 *	</ul>
+	 * Same as {@link #encoders(Class...)} except input a pre-constructed instances.
 	 *
-	 * @param converters The converter classes to add to this config.
+	 * @param encoders Encoder instances to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder converters(RestConverter...converters) {
-		return addTo(REST_converters, converters);
+	public RestContextBuilder encoders(Encoder...encoders) {
+		return addTo(REST_encoders, encoders);
 	}
 
 	/**
@@ -1164,30 +1163,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * <b>Configuration property:</b>  Class-level guards.
 	 *
 	 * <p>
-	 * Associates one or more {@link RestGuard RestGuards} with all REST methods defined in this class.
-	 * These guards get called immediately before execution of any REST method in this class.
-	 *
-	 * <p>
-	 * Typically, guards will be used for permissions checking on the user making the request, but it can also be used
-	 * for other purposes like pre-call validation of a request.
-	 *
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_guards}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#guards()}
-	 * 			<li>{@link RestMethod#guards()}
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#guards(Class...)}
-	 * 			<li>{@link RestContextBuilder#guards(RestGuard...)}
-	 * 		</ul>
-	 * 	<li>{@link RestGuard} classes must have either a no-arg or {@link PropertyStore} argument constructors.
-	 * 	<li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
-	 * 		annotation.
-	 *	</ul>
+	 * Same as {@link #guards(Class...)} except input is pre-constructed instances.
 	 *
 	 * @param guards The guard classes to add to this config.
 	 * @return This object (for method chaining).
@@ -1197,442 +1173,556 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Response handlers.
+	 * <b>Configuration property:</b>  REST info provider. 
 	 *
 	 * <p>
-	 * Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
-	 * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+	 * Class used to retrieve title/description/swagger information about a resource.
 	 *
 	 * <p>
-	 * By default, the following response handlers are provided out-of-the-box:
-	 * <ul>
-	 * 	<li>{@link StreamableHandler}
-	 * 	<li>{@link WritableHandler}
-	 * 	<li>{@link ReaderHandler}
-	 * 	<li>{@link InputStreamHandler}
-	 * 	<li>{@link RedirectHandler}
-	 * 	<li>{@link DefaultHandler}
-	 * </ul>
-
+	 * Subclasses can be used to customize the documentation on a resource.
+	 *
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_responseHandlers}
+	 * 	<li>Property:  {@link RestContext#REST_infoProvider}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#responseHandlers()} 
+	 * 			<li>{@link RestResource#infoProvider()} 
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#responseHandlers(Class...)}
-	 * 			<li>{@link RestContextBuilder#responseHandlers(ResponseHandler...)}
+	 * 			<li>{@link RestContextBuilder#infoProvider(Class)}
+	 * 			<li>{@link RestContextBuilder#infoProvider(RestInfoProvider)} 
 	 * 		</ul>
-	 * 	<li>{@link ResponseHandler} classes must have either a no-arg or {@link PropertyStore} argument constructors.
 	 *	</ul>
 	 *
-	 * @param responseHandlers The response handlers to add to this config.
+	 * @param infoProvider The new info provider for this resource.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder responseHandlers(Class<?>...responseHandlers) {
-		return addTo(REST_responseHandlers, responseHandlers);
+	public RestContextBuilder infoProvider(Class<? extends RestInfoProvider> infoProvider) {
+		return set(REST_infoProvider, infoProvider);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Response handlers.
+	 * <b>Configuration property:</b>  REST info provider. 
 	 *
 	 * <p>
-	 * Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
-	 * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+	 * Same as {@link #infoProvider(Class)} except input is a pre-constructed instance.
 	 *
-	 * <p>
-	 * By default, the following response handlers are provided out-of-the-box:
-	 * <ul>
-	 * 	<li>{@link StreamableHandler}
-	 * 	<li>{@link WritableHandler}
-	 * 	<li>{@link ReaderHandler}
-	 * 	<li>{@link InputStreamHandler}
-	 * 	<li>{@link RedirectHandler}
-	 * 	<li>{@link DefaultHandler}
-	 * </ul>
+	 * @param infoProvider The new info provider for this resource.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder infoProvider(RestInfoProvider infoProvider) {
+		return set(REST_infoProvider, infoProvider);
+	}
 
+	/**
+	 * <b>Configuration property:</b>  REST logger.
+	 * 
+	 * <p>
+	 * Specifies the logger to use for logging.
+	 *
+	 * <p>
+	 * The default logger performs basic error logging to the Java logger.
+	 * <br>Subclasses can be used to customize logging behavior on the resource.
+	 * 
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_responseHandlers}
+	 * 	<li>Property:  {@link RestContext#REST_logger}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#responseHandlers()} 
+	 * 			<li>{@link RestResource#logger()} 
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#responseHandlers(Class...)}
-	 * 			<li>{@link RestContextBuilder#responseHandlers(ResponseHandler...)}
+	 * 			<li>{@link RestContextBuilder#logger(Class)}
+	 * 			<li>{@link RestContextBuilder#logger(RestLogger)} 
 	 * 		</ul>
-	 * 	<li>{@link ResponseHandler} classes must have either a no-arg or {@link PropertyStore} argument constructors.
 	 *	</ul>
 	 *
-	 * @param responseHandlers The response handlers to add to this config.
+	 * @param logger The new logger for this resource.  Can be <jk>null</jk> to disable logging.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder responseHandlers(ResponseHandler...responseHandlers) {
-		return addTo(REST_responseHandlers, responseHandlers);
+	public RestContextBuilder logger(Class<? extends RestLogger> logger) {
+		return set(REST_logger, logger);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Default request headers.
-	 *
+	 * <b>Configuration property:</b>  REST logger.
+	 * 
 	 * <p>
-	 * Adds class-level default HTTP request headers to this resource.
+	 * Same as {@link #logger(Class)} except input is a pre-constructed instance.
+	 *
+	 * @param logger The new logger for this resource.  Can be <jk>null</jk> to disable logging.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder logger(RestLogger logger) {
+		return set(REST_logger, logger);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  The maximum allowed input size (in bytes) on HTTP requests.
 	 *
 	 * <p>
+	 * Useful for alleviating DoS attacks by throwing an exception when too much input is received instead of resulting
+	 * in out-of-memory errors which could affect system stability.
+	 * 
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_defaultRequestHeaders}
+	 * 	<li>Property:  {@link RestContext#REST_maxInput}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#defaultRequestHeaders()}
-	 * 			<li>{@link RestMethod#defaultRequestHeaders()} 
+	 * 			<li>{@link RestResource#maxInput()}
+	 * 			<li>{@link RestMethod#maxInput()}
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#defaultRequestHeader(String,Object)}
-	 * 			<li>{@link RestContextBuilder#defaultRequestHeaders(String...)}
+	 * 			<li>{@link RestContextBuilder#maxInput(String)}
 	 * 		</ul>
-	 * 	<li>Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
-	 * 	<li>The most useful reason for this annotation is to provide a default <code>Accept</code> header when one is not
-	 * 		specified so that a particular default {@link Serializer} is picked.
+	 * 	<li>String value that gets resolved to a <jk>long</jk>.
+	 * 	<li>Can be suffixed with any of the following representing kilobytes, megabytes, and gigabytes:  
+	 * 		<js>'K'</js>, <js>'M'</js>, <js>'G'</js>.
+	 * 	<li>A value of <js>"-1"</js> can be used to represent no limit.
 	 *	</ul>
 	 *
-	 * @param name The HTTP header name.
-	 * @param value The HTTP header value.
+	 * @param value The new value for this setting.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder defaultRequestHeader(String name, Object value) {
-		return addTo(REST_defaultRequestHeaders, name, value);
+	public RestContextBuilder maxInput(String value) {
+		return set(REST_maxInput, value);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Default request headers.
+	 * <b>Configuration property:</b>  Messages. 
 	 *
 	 * <p>
-	 * Adds class-level default HTTP request headers to this resource.
+	 * Identifies the location of the resource bundle for this class.
 	 *
 	 * <p>
-	 * <h5 class='section'>Notes:</h5>
+	 * This annotation is used to provide localized messages for the following methods:
+	 * <ul>
+	 * 	<li>{@link RestRequest#getMessage(String, Object...)}
+	 * 	<li>{@link RestContext#getMessages()}
+	 * </ul>
+	 *
+	 * <p>
+	 * Refer to the {@link MessageBundle} class for a description of the message key formats used in the properties file.
+	 *
+	 * <p>
+	 * The value can be a relative path like <js>"nls/Messages"</js>, indicating to look for the resource bundle
+	 * <js>"com.foo.sample.nls.Messages"</js> if the resource class is in <js>"com.foo.sample"</js>, or it can be an
+	 * absolute path, like <js>"com.foo.sample.nls.Messages"</js>
+	 * 
+	 * <h6 class='topic'>Notes:</h6>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_defaultRequestHeaders}
-	 * 	<li>Annotations:
+	 * 	<li>Property:  {@link RestContext#REST_messages}
+	 * 	<li>Annotations: 
 	 * 		<ul>
-	 * 			<li>{@link RestResource#defaultRequestHeaders()}
-	 * 			<li>{@link RestMethod#defaultRequestHeaders()} 
+	 * 			<li>{@link RestResource#messages()} 
 	 * 		</ul>
-	 * 	<li>Methods:
+	 * 	<li>Methods: 
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#defaultRequestHeader(String,Object)}
-	 * 			<li>{@link RestContextBuilder#defaultRequestHeaders(String...)}
+	 * 			<li>{@link RestContextBuilder#messages(String)},
+	 * 			<li>{@link RestContextBuilder#messages(Class,String)}
+	 * 			<li>{@link RestContextBuilder#messages(MessageBundleLocation)} 
 	 * 		</ul>
-	 * 	<li>Strings are of the format <js>"Header-Name: header-value"</js>.
-	 * 	<li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
-	 * 	<li>Key and value is trimmed of whitespace.
-	 * 	<li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
-	 * 	<li>Affects values returned by {@link RestRequest#getHeader(String)} when the header is not present on the request.
-	 * 	<li>The most useful reason for this annotation is to provide a default <code>Accept</code> header when one is not
-	 * 		specified so that a particular default {@link Serializer} is picked.
-	 *	</ul>
+	 * 	<li>Mappings are cumulative from parent to child.  
+	 * </ul>
 	 *
-	 * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
+	 * @param messageBundleLocation The message bundle location to add to the search path.
 	 * @return This object (for method chaining).
-	 * @throws RestServletException If malformed header is found.
 	 */
-	public RestContextBuilder defaultRequestHeaders(String...headers) throws RestServletException {
-		for (String header : headers) {
-			String[] h = RestUtils.parseHeader(header);
-			if (h == null)
-				throw new RestServletException("Invalid default request header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
-			defaultRequestHeader(h[0], h[1]);
-		}
-		return this;
+	public RestContextBuilder messages(MessageBundleLocation messageBundleLocation) {
+		return addTo(REST_messages, messageBundleLocation);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Default response headers.
+	 * <b>Configuration property:</b>  Messages. 
 	 *
 	 * <p>
-	 * Specifies default values for response headers.
+	 * Same as {@link #messages(MessageBundleLocation)} except allows you to pass in the base class and bundle
+	 * path separately.
+	 * 
+	 * @param baseClass 
+	 * 	The base class that the bundle path is relative to.
+	 * 	<br>If <jk>null</jk>, assumed to be the resource class itself.
+	 * @param bundlePath The bundle path relative to the base class.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder messages(Class<?> baseClass, String bundlePath) {
+		return addTo(REST_messages, new MessageBundleLocation(baseClass, bundlePath));
+	}
+	
+	/**
+	 * <b>Configuration property:</b>  Messages. 
 	 *
 	 * <p>
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_defaultResponseHeaders}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#defaultResponseHeaders()} 
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#defaultResponseHeader(String,Object)}
-	 * 			<li>{@link RestContextBuilder#defaultResponseHeaders(String...)}
-	 * 		</ul>
-	 * 	<li>This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of 
-	 * 		the Java methods.
-	 * 	<li>The header value will not be set if the header value has already been specified (hence the 'default' in the name).
-	 * 	<li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
-	 * 		annotation.
-	 *	</ul>
-	 *
-	 * @param name The HTTP header name.
-	 * @param value The HTTP header value.
+	 * Same as {@link #messages(Class,String)} except assumes the base class is the resource class itself.
+	 * 
+	 * @param bundlePath The bundle path relative to the base class.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder defaultResponseHeader(String name, Object value) {
-		return addTo(REST_defaultResponseHeaders, name, value);
+	public RestContextBuilder messages(String bundlePath) {
+		return addTo(REST_messages, new MessageBundleLocation(null, bundlePath));
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Default response headers.
+	 * <b>Configuration property:</b>  MIME types. 
 	 *
 	 * <p>
-	 * Specifies default values for response headers.
-	 *
+	 * Defines MIME-type file type mappings.
+	 * 
 	 * <p>
-	 * <h5 class='section'>Notes:</h5>
+	 * Used for specifying the content type on file resources retrieved through the following methods:
+	 * <ul>
+	 * 	<li>{@link RestContext#resolveStaticFile(String)}
+	 * 	<li>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType)}
+	 * 	<li>{@link RestRequest#getClasspathReaderResource(String,boolean)}
+	 * 	<li>{@link RestRequest#getClasspathReaderResource(String)}
+	 * </ul>
+	 * 
+	 * <p>
+	 * This list appends to the existing list provided by {@link ExtendedMimetypesFileTypeMap}.
+	 * 
+	 * <h6 class='topic'>Notes:</h6>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_defaultResponseHeaders}
-	 * 	<li>Annotations:
+	 * 	<li>Property:  {@link RestContext#REST_mimeTypes}
+	 * 	<li>Annotations: 
 	 * 		<ul>
-	 * 			<li>{@link RestResource#defaultResponseHeaders()} 
+	 * 			<li>{@link RestResource#mimeTypes()} 
 	 * 		</ul>
-	 * 	<li>Methods:
+	 * 	<li>Methods: 
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#defaultResponseHeader(String,Object)}
-	 * 			<li>{@link RestContextBuilder#defaultResponseHeaders(String...)}
+	 * 			<li>{@link RestContextBuilder#mimeTypes(String...)}
 	 * 		</ul>
-	 * 	<li>Strings are of the format <js>"Header-Name: header-value"</js>.
-	 * 	<li>You can use either <js>':'</js> or <js>'='</js> as the key/value delimiter.
-	 * 	<li>Key and value is trimmed of whitespace.
-	 * 	<li>Only one header value can be specified per entry (i.e. it's not a delimited list of header entries).
-	 * 	<li>This is equivalent to calling {@link RestResponse#setHeader(String, String)} programmatically in each of 
-	 * 		the Java methods.
-	 * 	<li>The header value will not be set if the header value has already been specified (hence the 'default' in the name).
-	 * 	<li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
-	 * 		annotation.
-	 *	</ul>
+	 * 	<li>Values are .mime.types formatted entry string.
+	 * 		<br>Example: <js>"image/svg+xml svg"</js>
+	 * </ul>
 	 *
-	 * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
+	 * @param mimeTypes The MIME-types to add to this config.
 	 * @return This object (for method chaining).
-	 * @throws RestServletException If malformed header is found.
 	 */
-	public RestContextBuilder defaultResponseHeaders(String...headers) throws RestServletException {
-		for (String header : headers) {
-			String[] h = RestUtils.parseHeader(header);
-			if (h == null)
-				throw new RestServletException("Invalid default response header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
-			defaultResponseHeader(h[0], h[1]);
-		}
-		return this;
+	public RestContextBuilder mimeTypes(String...mimeTypes) {
+		return addTo(REST_mimeTypes, mimeTypes);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Supported accept media types.
+	 * <b>Configuration property:</b>  Java method parameter resolvers.
 	 *
 	 * <p>
-	 * Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
-	 * 
-	 * <p>
-	 * This affects the values returned by {@link RestRequest#getSupportedAcceptTypes()} and the supported accept
-	 * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+	 * By default, the Juneau framework will automatically Java method parameters of various types (e.g.
+	 * <code>RestRequest</code>, <code>Accept</code>, <code>Reader</code>).
+	 * This annotation allows you to provide your own resolvers for your own class types that you want resolved.
 	 *
 	 * <p>
+	 * For example, if you want to pass in instances of <code>MySpecialObject</code> to your Java method, define
+	 * the following resolver:
+	 * <p class='bcode'>
+	 * 	<jk>public class</jk> MyRestParam <jk>extends</jk> RestParam {
+	 *
+	 * 		<jc>// Must have no-arg constructor!</jc>
+	 * 		<jk>public</jk> MyRestParam() {
+	 * 			<jc>// First two parameters help with Swagger doc generation.</jc>
+	 * 			<jk>super</jk>(<jsf>QUERY</jsf>, <js>"myparam"</js>, MySpecialObject.<jk>class</jk>);
+	 * 		}
+	 *
+	 * 		<jc>// The method that creates our object.
+	 * 		// In this case, we're taking in a query parameter and converting it to our object.</jc>
+	 * 		<jk>public</jk> Object resolve(RestRequest req, RestResponse res) <jk>throws</jk> Exception {
+	 * 			<jk>return new</jk> MySpecialObject(req.getQuery().get(<js>"myparam"</js>));
+	 * 		}
+	 * 	}
+	 * </p>
+	 *
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_supportedAcceptTypes}
+	 * 	<li>Property:  {@link RestContext#REST_paramResolvers}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#supportedAcceptTypes()}
-	 * 			<li>{@link RestMethod#supportedAcceptTypes()}
-	 * 		</ul> 
-	 * 	<li>Methods:  
+	 * 			<li>{@link RestResource#paramResolvers()}
+	 * 		</ul>
+	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#supportedAcceptTypes(boolean,String...)}
-	 * 			<li>{@link RestContextBuilder#supportedAcceptTypes(boolean,MediaType...)}
+	 * 			<li>{@link RestContextBuilder#paramResolvers(Class...)}
 	 * 		</ul>
+	 * 	<li>{@link RestParam} classes must have either a no-arg or {@link PropertyStore} argument constructors.
 	 *	</ul>
 	 *
-	 * @param append
-	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
-	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @param paramResolvers The parameter resolvers to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder supportedAcceptTypes(boolean append, String...mediaTypes) {
-		return set(append, REST_supportedAcceptTypes, mediaTypes);
+	@SuppressWarnings("unchecked")
+	public RestContextBuilder paramResolvers(Class<? extends RestParam>...paramResolvers) {
+		return addTo(REST_paramResolvers, paramResolvers);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Supported accept media types.
+	 * <b>Configuration property:</b>  Java method parameter resolvers.
 	 *
 	 * <p>
-	 * Overrides the media types inferred from the serializers that identify what media types can be produced by the resource.
+	 * Same as {@link #paramResolvers(Class...)} except input is pre-constructed instances.
+	 *
+	 * @param paramResolvers The parameter resolvers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder paramResolvers(RestParam...paramResolvers) {
+		return addTo(REST_paramResolvers, paramResolvers);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Parser listener.
 	 * 
 	 * <p>
-	 * This affects the values returned by {@link RestRequest#getSupportedAcceptTypes()} and the supported accept
-	 * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+	 * Specifies the parser listener class to use for listening to non-fatal parsing errors.
 	 *
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_supportedAcceptTypes}
+	 * 	<li>Property:  {@link Parser#PARSER_listener}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#supportedAcceptTypes()}
-	 * 			<li>{@link RestMethod#supportedAcceptTypes()}
-	 * 		</ul> 
-	 * 	<li>Methods:  
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#supportedAcceptTypes(boolean,String...)}
-	 * 			<li>{@link RestContextBuilder#supportedAcceptTypes(boolean,MediaType...)}
+	 * 			<li>{@link RestResource#parserListener()} 
 	 * 		</ul>
+	 * 	<li>Methods:
+	 * 		<ul>
+	 * 			<li>{@link RestContextBuilder#parserListener(Class)}
+	 * 		</ul> 
 	 *	</ul>
 	 *
-	 * @param append
-	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
-	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @param listener The listener to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder supportedAcceptTypes(boolean append, MediaType...mediaTypes) {
-		return set(append, REST_supportedAcceptTypes, mediaTypes);
+	public RestContextBuilder parserListener(Class<? extends ParserListener> listener) {
+		return set(PARSER_listener, listener);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Supported content media types.
-	 *
-	 * <p>
-	 * Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
+	 * <b>Configuration property:</b>  Parsers. 
 	 *
 	 * <p>
-	 * This affects the values returned by {@link RestRequest#getSupportedContentTypes()} and the supported content
-	 * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+	 * Adds class-level parsers to this resource.
 	 * 
-	 * <p>
-	 * <h5 class='section'>Notes:</h5>
+	 * <h6 class='topic'>Notes:</h6>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_supportedContentTypes}
-	 * 	<li>Annotations:
+	 * 	<li>Property:  {@link RestContext#REST_parsers}
+	 * 	<li>Annotations: 
 	 * 		<ul>
-	 * 			<li>{@link RestResource#supportedContentTypes()}
-	 * 			<li>{@link RestMethod#supportedContentTypes()}
-	 * 		</ul> 
-	 * 	<li>Methods:  
+	 * 			<li>{@link RestResource#parsers()} 
+	 * 			<li>{@link RestMethod#parsers()} 
+	 * 		</ul>
+	 * 	<li>Methods: 
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#supportedContentTypes(boolean,String...)}
-	 * 			<li>{@link RestContextBuilder#supportedContentTypes(boolean,MediaType...)}
+	 * 			<li>{@link RestContextBuilder#parsers(Class...)}
+	 * 			<li>{@link RestContextBuilder#parsers(boolean,Class...)}
+	 * 			<li>{@link RestContextBuilder#parsers(Parser...)}
+	 * 			<li>{@link RestContextBuilder#parsers(boolean,Parser...)}
 	 * 		</ul>
-	 *	</ul>
+	 * 	<li>When defined as a class, properties/transforms defined on the resource/method are inherited.
+	 * 	<li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
+	 * 	<li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
+	 * 		annotation.
+	 * </ul>
+	 *
+	 * @param parsers The parser classes to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder parsers(Class<?>...parsers) {
+		return addTo(REST_parsers, parsers);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Parsers. 
 	 *
+	 * <p>
+	 * Same as {@link #parsers(Class...)} except allows you to overwrite the previous value.
+	 * 
 	 * @param append
 	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
-	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @param parsers The parser classes to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder supportedContentTypes(boolean append, String...mediaTypes) {
-		return set(append, REST_supportedContentTypes, mediaTypes);
+	public RestContextBuilder parsers(boolean append, Class<?>...parsers) {
+		return set(append, REST_parsers, parsers);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Supported content media types.
+	 * <b>Configuration property:</b>  Parsers. 
 	 *
 	 * <p>
-	 * Overrides the media types inferred from the parsers that identify what media types can be consumed by the resource.
+	 * Same as {@link #parsers(Class...)} except input is pre-constructed instances.
+	 * 
+	 * <p>
+	 * Parser instances are considered set-in-stone and do NOT inherit properties and transforms defined on the
+	 * resource class or method. 
+	 * 
+	 * @param parsers The parsers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder parsers(Parser...parsers) {
+		return addTo(REST_parsers, parsers);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Parsers. 
 	 *
 	 * <p>
-	 * This affects the values returned by {@link RestRequest#getSupportedContentTypes()} and the supported content
-	 * types shown in {@link RestInfoProvider#getSwagger(RestRequest)}.
+	 * Same as {@link #parsers(Parser...)} except allows you to overwrite the previous value.
 	 * 
+	 * @param append
+	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
+	 * @param parsers The parsers to add to this config.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder parsers(boolean append, Parser...parsers) {
+		return set(append, REST_parsers, parsers);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  HTTP part parser. 
+	 *
 	 * <p>
-	 * <h5 class='section'>Notes:</h5>
+	 * Specifies the {@link HttpPartParser} to use for parsing headers, query/form parameters, and URI parts.
+	 * 
+	 * <h6 class='topic'>Notes:</h6>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_supportedContentTypes}
-	 * 	<li>Annotations:
+	 * 	<li>Property:  {@link RestContext#REST_partParser}
+	 * 	<li>Annotations: 
 	 * 		<ul>
-	 * 			<li>{@link RestResource#supportedContentTypes()}
-	 * 			<li>{@link RestMethod#supportedContentTypes()}
-	 * 		</ul> 
-	 * 	<li>Methods:  
+	 * 			<li>{@link RestResource#partParser()} 
+	 * 		</ul>
+	 * 	<li>Methods: 
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#supportedContentTypes(boolean,String...)}
-	 * 			<li>{@link RestContextBuilder#supportedContentTypes(boolean,MediaType...)}
+	 * 			<li>{@link RestContextBuilder#partParser(Class)}
+	 * 			<li>{@link RestContextBuilder#partParser(HttpPartParser)}
 	 * 		</ul>
-	 *	</ul>
+	 * 	<li>When defined as a class, properties/transforms defined on the resource/method are inherited.
+	 * 	<li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
+	 * </ul>
 	 *
-	 * @param append
-	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
-	 * @param mediaTypes The new list of media types supported by this resource.
+	 * @param partParser The parser class.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder supportedContentTypes(boolean append, MediaType...mediaTypes) {
-		return set(append, REST_supportedContentTypes, mediaTypes);
+	public RestContextBuilder partParser(Class<? extends HttpPartParser> partParser) {
+		return set(REST_partParser, partParser);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Client version header.
+	 * <b>Configuration property:</b>  HTTP part parser. 
 	 *
 	 * <p>
-	 * Specifies the name of the header used to denote the client version on HTTP requests.
+	 * Same as {@link #partParser(Class)} except input is a pre-constructed instance.
+	 *
+	 * @param partParser The parser instance.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder partParser(HttpPartParser partParser) {
+		return set(REST_partParser, partParser);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  HTTP part serializer. 
 	 *
 	 * <p>
-	 * The client version is used to support backwards compatibility for breaking REST interface changes.
-	 * <br>Used in conjunction with {@link RestMethod#clientVersion()} annotation.
-	 * 
+	 * Specifies the {@link HttpPartSerializer} to use for serializing headers, query/form parameters, and URI parts.
+	 *
+	 * <h6 class='topic'>Notes:</h6>
+	 * <ul class='spaced-list'>
+	 * 	<li>Property:  {@link RestContext#REST_partSerializer}
+	 * 	<li>Annotations: 
+	 * 		<ul>
+	 * 			<li>{@link RestResource#partSerializer()} 
+	 * 		</ul>
+	 * 	<li>Methods: 
+	 * 		<ul>
+	 * 			<li>{@link RestContextBuilder#partSerializer(Class)}
+	 * 			<li>{@link RestContextBuilder#partSerializer(HttpPartSerializer)}
+	 * 		</ul>
+	 * 	<li>When defined as a class, properties/transforms defined on the resource/method are inherited.
+	 * 	<li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
+	 * </ul>
+	 *
+	 * @param partSerializer The serializer class.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder partSerializer(Class<? extends HttpPartSerializer> partSerializer) {
+		return set(REST_partSerializer, partSerializer);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  HTTP part serializer. 
+	 *
+	 * <p>
+	 * Same as {@link #partSerializer(Class)} except input is a pre-constructed instance.
+	 *
+	 * @param partSerializer The serializer instance.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder partSerializer(HttpPartSerializer partSerializer) {
+		return set(REST_partSerializer, partSerializer);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Resource path.   
+	 *
+	 * <p>
+	 * Identifies the URL subpath relative to the parent resource.
+	 *
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_clientVersionHeader}
+	 * 	<li>Property:  {@link RestContext#REST_path}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#clientVersionHeader()} 
-	 * 		</ul>
+	 * 			<li>{@link RestResource#path()}
+	 * 		</ul> 
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#clientVersionHeader(String)}
+	 * 			<li>{@link RestContextBuilder#path(String)} 
 	 * 		</ul>
+	 * 	<li>This annotation is ignored on top-level servlets (i.e. servlets defined in <code>web.xml</code> files).
+	 * 		<br>Therefore, implementers can optionally specify a path value for documentation purposes.
+	 * 	<li>Typically, this setting is only applicable to resources defined as children through the 
+	 * 		{@link RestResource#children()} annotation.
+	 * 		<br>However, it may be used in other ways (e.g. defining paths for top-level resources in microservices).
 	 *	</ul>
 	 *
-	 * @param clientVersionHeader The name of the HTTP header that denotes the client version.
+	 * @param path The URL path of this resource.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder clientVersionHeader(String clientVersionHeader) {
-		return set(REST_clientVersionHeader, clientVersionHeader);
+	public RestContextBuilder path(String path) {
+		if (startsWith(path, '/'))
+			path = path.substring(1);
+		this.path = path;
+		return this;
 	}
 
 	/**
-	 * REST resource resolver.
-	 * 
-	 * <p>
-	 * The resolver used for resolving child resources.
-	 * 
-	 * <p>
-	 * Can be used to provide customized resolution of REST resource class instances (e.g. resources retrieve from Spring).
+	 * <b>Configuration property:</b>  Render response stack traces in responses.
 	 *
 	 * <p>
+	 * Render stack traces in HTTP response bodies when errors occur.
+	 *
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_resourceResolver}
+	 * 	<li>Property:  {@link RestContext#REST_renderResponseStackTraces}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#resourceResolver()} 
+	 * 			<li>{@link RestResource#renderResponseStackTraces()}
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#resourceResolver(Class)}
-	 * 			<li>{@link RestContextBuilder#resourceResolver(RestResourceResolver)}
+	 * 			<li>{@link RestContextBuilder#renderResponseStackTraces(boolean)}
 	 * 		</ul>
-	 * 	<li>Unless overridden, resource resolvers are inherited from parent resources.
+	 * 	<li>Useful for debugging, although allowing stack traces to be rendered may cause security concerns so use
+	 * 		caution when enabling.
 	 *	</ul>
 	 *
-	 * @param resourceResolver The new resource resolver.
+	 * @param value The new value for this setting.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder resourceResolver(Class<? extends RestResourceResolver> resourceResolver) {
-		return set(REST_resourceResolver, resourceResolver);
+	public RestContextBuilder renderResponseStackTraces(boolean value) {
+		return set(REST_renderResponseStackTraces, value);
 	}
 
 	/**
@@ -1655,673 +1745,110 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * 	<li>Methods:
 	 * 		<ul>
 	 * 			<li>{@link RestContextBuilder#resourceResolver(Class)}
-					<li>{@link RestContextBuilder#resourceResolver(RestResourceResolver)}
-				</ul>
+	 * 			<li>{@link RestContextBuilder#resourceResolver(RestResourceResolver)}
+	 * 		</ul>
 	 * 	<li>Unless overridden, resource resolvers are inherited from parent resources.
 	 *	</ul>
 	 *
 	 * @param resourceResolver The new resource resolver.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder resourceResolver(RestResourceResolver resourceResolver) {
+	public RestContextBuilder resourceResolver(Class<? extends RestResourceResolver> resourceResolver) {
 		return set(REST_resourceResolver, resourceResolver);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  REST logger.
+	 * REST resource resolver.
 	 * 
 	 * <p>
-	 * Specifies the logger to use for logging.
+	 * Same as {@link #resourceResolver(Class)} except input is a pre-constructed instance.
+	 *
+	 * @param resourceResolver The new resource resolver.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder resourceResolver(RestResourceResolver resourceResolver) {
+		return set(REST_resourceResolver, resourceResolver);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Response handlers.
 	 *
 	 * <p>
-	 * The default logger performs basic error logging to the Java logger.
-	 * <br>Subclasses can be used to customize logging behavior on the resource.
-	 * 
+	 * Specifies a list of {@link ResponseHandler} classes that know how to convert POJOs returned by REST methods or
+	 * set via {@link RestResponse#setOutput(Object)} into appropriate HTTP responses.
+	 *
+	 * <p>
+	 * By default, the following response handlers are provided out-of-the-box:
+	 * <ul>
+	 * 	<li>{@link StreamableHandler}
+	 * 	<li>{@link WritableHandler}
+	 * 	<li>{@link ReaderHandler}
+	 * 	<li>{@link InputStreamHandler}
+	 * 	<li>{@link RedirectHandler}
+	 * 	<li>{@link DefaultHandler}
+	 * </ul>
+
 	 * <p>
 	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_logger}
+	 * 	<li>Property:  {@link RestContext#REST_responseHandlers}
 	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#logger()} 
+	 * 			<li>{@link RestResource#responseHandlers()} 
 	 * 		</ul>
 	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#logger(Class)}
-	 * 			<li>{@link RestContextBuilder#logger(RestLogger)} 
-	 * 		</ul>
-	 *	</ul>
-	 *
-	 * @param logger The new logger for this resource.  Can be <jk>null</jk> to disable logging.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder logger(Class<? extends RestLogger> logger) {
-		return set(REST_logger, logger);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  REST logger.
-	 * 
-	 * <p>
-	 * Specifies the logger to use for logging.
-	 *
-	 * <p>
-	 * The default logger performs basic error logging to the Java logger.
-	 * <br>Subclasses can be used to customize logging behavior on the resource.
-	 *
-	 * <p>
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_logger}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#logger()} 
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#logger(Class)}
-	 * 			<li>{@link RestContextBuilder#logger(RestLogger)} 
-	 * 		</ul>
-	 *	</ul>
-	 *
-	 * @param logger The new logger for this resource.  Can be <jk>null</jk> to disable logging.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder logger(RestLogger logger) {
-		return set(REST_logger, logger);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  REST info provider. 
-	 *
-	 * <p>
-	 * Class used to retrieve title/description/swagger information about a resource.
-	 *
-	 * <p>
-	 * Subclasses can be used to customize the documentation on a resource.
-	 *
-	 * <p>
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_infoProvider}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#infoProvider()} 
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#infoProvider(Class)}
-	 * 			<li>{@link RestContextBuilder#infoProvider(RestInfoProvider)} 
-	 * 		</ul>
-	 *	</ul>
-	 *
-	 * @param infoProvider The new info provider for this resource.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder infoProvider(Class<? extends RestInfoProvider> infoProvider) {
-		return set(REST_infoProvider, infoProvider);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  REST info provider. 
-	 *
-	 * <p>
-	 * Class used to retrieve title/description/swagger information about a resource.
-	 *
-	 * <p>
-	 * Subclasses can be used to customize the documentation on a resource.
-	 *
-	 * <p>
-	 * <h5 class='section'>Notes:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_infoProvider}
-	 * 	<li>Annotations:
-	 * 		<ul>
-	 * 			<li>{@link RestResource#infoProvider()} 
-	 * 		</ul>
-	 * 	<li>Methods:
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#infoProvider(Class)}
-	 * 			<li>{@link RestContextBuilder#infoProvider(RestInfoProvider)} 
+	 * 			<li>{@link RestContextBuilder#responseHandlers(Class...)}
+	 * 			<li>{@link RestContextBuilder#responseHandlers(ResponseHandler...)}
 	 * 		</ul>
+	 * 	<li>{@link ResponseHandler} classes must have either a no-arg or {@link PropertyStore} argument constructors.
 	 *	</ul>
 	 *
-	 * @param infoProvider The new info provider for this resource.
+	 * @param responseHandlers The response handlers to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder infoProvider(RestInfoProvider infoProvider) {
-		return set(REST_infoProvider, infoProvider);
+	public RestContextBuilder responseHandlers(Class<?>...responseHandlers) {
+		return addTo(REST_responseHandlers, responseHandlers);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Static file mappings. 
+	 * <b>Configuration property:</b>  Response handlers.
 	 *
 	 * <p>
-	 * Used to define paths and locations of statically-served files such as images or HTML documents.
-	 * 
-	 * <p>
-	 * Static files are found by calling {@link RestContext#getClasspathResource(String,Locale)} which uses the registered 
-	 * {@link ClasspathResourceFinder} for locating files on the classpath (or other location).
-	 * 
-	 * <p>
-	 * An example where this class is used is in the {@link RestResource#staticFiles} annotation:
-	 * <p class='bcode'>
-	 * 	<jk>package</jk> com.foo.mypackage;
-	 * 
-	 * 	<ja>@RestResource</ja>(
-	 * 		path=<js>"/myresource"</js>,
-	 * 		staticFiles=<js>"htdocs:docs"</js>
-	 * 	)
-	 * 	<jk>public class</jk> MyResource <jk>extends</jk> RestServletDefault {...}
-	 * </p>
-	 * 
-	 * <p>
-	 * In the example above, given a GET request to <l>/myresource/htdocs/foobar.html</l>, the servlet will attempt to find 
-	 * the <l>foobar.html</l> file in the following ordered locations:
-	 * <ol>
-	 * 	<li><l>com.foo.mypackage.docs</l> package.
-	 * 	<li><l>org.apache.juneau.rest.docs</l> package (since <l>RestServletDefault</l> is in <l>org.apache.juneau.rest</l>).
-	 * 	<li><l>[working-dir]/docs</l> directory.
-	 * </ol>
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_staticFiles}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#staticFiles()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#staticFiles(String)},
-	 * 			<li>{@link RestContextBuilder#staticFiles(Class,String)}
-	 * 			<li>{@link RestContextBuilder#staticFiles(String,String)}
-	 * 			<li>{@link RestContextBuilder#staticFiles(Class,String,String)} 
-	 * 			<li>{@link RestContextBuilder#staticFiles(StaticFileMapping...)} 
-	 * 		</ul>
-	 * 	<li>Mappings are cumulative from parent to child.  
-	 * 	<li>Child resources can override mappings made on parent resources.
-	 * 	<li>The media type on the response is determined by the {@link RestContext#getMediaTypeForName(String)} method.
-	 * 	<li>The resource finder is configured via the {@link RestContext#REST_classpathResourceFinder} setting, and can be
-	 * 		overridden to provide customized handling of resource retrieval.
-	 * 	<li>The {@link RestContext#REST_useClasspathResourceCaching} setting can be used to cache static files in memory
-	 * 		to improve performance.
-	 * </ul>
+	 * Same as {@link #responseHandlers(Class...)} except input is pre-constructed instances.
 	 *
-	 * @param sfm The static file mappings to add to this resource.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder staticFiles(StaticFileMapping...sfm) {
-		return addTo(REST_staticFiles, sfm);
-	}
-
-	/**
-	 * Same as {@link #staticFiles(StaticFileMapping...)}, except input is in the form of a mapping string.
-	 * 
-	 * <p>
-	 * Mapping string must be one of these formats:
-	 * <ul>
-	 * 	<li><js>"path:location"</js> (e.g. <js>"foodocs:docs/foo"</js>)
-	 * 	<li><js>"path:location:headers-json"</js> (e.g. <js>"foodocs:docs/foo:{'Cache-Control':'max-age=86400, public'}"</js>)
-	 * </ul>
-	 * 
-	 * @param mappingString The static file mapping string.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder staticFiles(String mappingString) {
-		return staticFiles(new StaticFileMapping(resourceClass, mappingString));
-	}
-
-	/**
-	 * Same as {@link #staticFiles(String)}, except overrides the base class for retrieving the resource.
-	 * 
-	 * <p>
-	 * Mapping string must be one of these formats:
-	 * <ul>
-	 * 	<li><js>"path:location"</js> (e.g. <js>"foodocs:docs/foo"</js>)
-	 * 	<li><js>"path:location:headers-json"</js> (e.g. <js>"foodocs:docs/foo:{'Cache-Control':'max-age=86400, public'}"</js>)
-	 * </ul>
-	 * 
-	 * @param baseClass 
-	 * 	Overrides the default class to use for retrieving the classpath resource. 
-	 * 	<br>If <jk>null<jk>, uses the REST resource class.
-	 * @param mappingString The static file mapping string.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder staticFiles(Class<?> baseClass, String mappingString) {
-		return staticFiles(new StaticFileMapping(baseClass, mappingString));
-	}
-	
-	/**
-	 * Same as {@link #staticFiles(String)}, except path and location are already split values.
-	 * 
-	 * @param path 
-	 * 	The mapped URI path.
-	 * 	<br>Leading and trailing slashes are trimmed.
-	 * @param location 
-	 * 	The location relative to the resource class.
-	 * 	<br>Leading and trailing slashes are trimmed.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder staticFiles(String path, String location) {
-		return staticFiles(new StaticFileMapping(null, path, location, null));
-	}
-
-	/**
-	 * Same as {@link #staticFiles(String,String)},  except overrides the base class for retrieving the resource.
-	 * 
-	 * @param baseClass 
-	 * 	Overrides the default class to use for retrieving the classpath resource. 
-	 * 	<br>If <jk>null<jk>, uses the REST resource class.
-	 * @param path 
-	 * 	The mapped URI path.
-	 * 	<br>Leading and trailing slashes are trimmed.
-	 * @param location 
-	 * 	The location relative to the resource class.
-	 * 	<br>Leading and trailing slashes are trimmed.
+	 * @param responseHandlers The response handlers to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder staticFiles(Class<?> baseClass, String path, String location) {
-		return staticFiles(new StaticFileMapping(baseClass, path, location, null));
+	public RestContextBuilder responseHandlers(ResponseHandler...responseHandlers) {
+		return addTo(REST_responseHandlers, responseHandlers);
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Messages. 
-	 *
-	 * <p>
-	 * Identifies the location of the resource bundle for this class.
-	 *
-	 * <p>
-	 * This annotation is used to provide localized messages for the following methods:
-	 * <ul>
-	 * 	<li>{@link RestRequest#getMessage(String, Object...)}
-	 * 	<li>{@link RestContext#getMessages()}
-	 * </ul>
-	 *
-	 * <p>
-	 * Refer to the {@link MessageBundle} class for a description of the message key formats used in the properties file.
-	 *
-	 * <p>
-	 * The value can be a relative path like <js>"nls/Messages"</js>, indicating to look for the resource bundle
-	 * <js>"com.foo.sample.nls.Messages"</js> if the resource class is in <js>"com.foo.sample"</js>, or it can be an
-	 * absolute path, like <js>"com.foo.sample.nls.Messages"</js>
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_messages}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#messages()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#messages(String)},
-	 * 			<li>{@link RestContextBuilder#messages(Class,String)}
-	 * 			<li>{@link RestContextBuilder#messages(MessageBundleLocation)} 
-	 * 		</ul>
-	 * 	<li>Mappings are cumulative from parent to child.  
-	 * </ul>
-	 *
-	 * @param messageBundleLocation The message bundle location to add to the search path.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder messages(MessageBundleLocation messageBundleLocation) {
-		return addTo(REST_messages, messageBundleLocation);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  Messages. 
-	 *
-	 * <p>
-	 * Same as {@link #messages(MessageBundleLocation)} except allows you to pass in the base class and bundle
-	 * path separately.
-	 * 
-	 * @param baseClass 
-	 * 	The base class that the bundle path is relative to.
-	 * 	<br>If <jk>null</jk>, assumed to be the resource class itself.
-	 * @param bundlePath The bundle path relative to the base class.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder messages(Class<?> baseClass, String bundlePath) {
-		return addTo(REST_messages, new MessageBundleLocation(baseClass, bundlePath));
-	}
-	
-	/**
-	 * <b>Configuration property:</b>  Messages. 
-	 *
-	 * <p>
-	 * Same as {@link #messages(Class,String)} except assumes the base class is the resource class itself.
-	 * 
-	 * @param bundlePath The bundle path relative to the base class.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder messages(String bundlePath) {
-		return addTo(REST_messages, new MessageBundleLocation(null, bundlePath));
-	}
-
-	/**
-	 * <b>Configuration property:</b>  Static file response headers. 
-	 *
-	 * <p>
-	 * Used to customize the headers on responses returned for statically-served files.
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_staticFileResponseHeaders}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#staticFileResponseHeaders()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
-	 * 		</ul>
-	 * </ul>
-	 * 
-	 * @param append
-	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
-	 * @param headers The headers to add to this list.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder staticFileResponseHeaders(boolean append, Map<String,String> headers) {
-		return set(append, REST_staticFileResponseHeaders, headers);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  Static file response headers. 
-	 *
-	 * <p>
-	 * Used to customize the headers on responses returned for statically-served files.
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_staticFileResponseHeaders}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#staticFileResponseHeaders()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
-	 * 		</ul>
-	 * </ul>
-	 * 
-	 * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
-	 * @return This object (for method chaining).
-	 * @throws RestServletException If malformed header is found.
-	 */
-	public RestContextBuilder staticFileResponseHeaders(String...headers) throws RestServletException {
-		for (String header : headers) {
-			String[] h = RestUtils.parseHeader(header);
-			if (h == null)
-				throw new RestServletException("Invalid static file response header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
-			staticFileResponseHeader(h[0], h[1]);
-		}
-		return this;
-	}
-
-	/**
-	 * <b>Configuration property:</b>  Static file response headers. 
-	 *
-	 * <p>
-	 * Used to customize the headers on responses returned for statically-served files.
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_staticFileResponseHeaders}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#staticFileResponseHeaders()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
-	 * 			<li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
-	 * 		</ul>
-	 * </ul>
-	 * 
-	 * @param name The HTTP header name.
-	 * @param value The HTTP header value.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder staticFileResponseHeader(String name, String value) {
-		return addTo(REST_staticFileResponseHeaders, name, value);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  Classpath resource finder. 
-	 * 
-	 * <p>
-	 * Used to retrieve localized files from the classpath.
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_classpathResourceFinder}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#classpathResourceFinder()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#classpathResourceFinder(Class)}
-	 * 			<li>{@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
-	 * 		</ul>
-	 * 	<li>
-	 * 		The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
-	 * 		resources on the classpath and JVM working directory.
-	 * 		<br>The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
-	 * 		up the parent class hierarchy.
-	 * 		<br>Each of these classes can be extended to provide customized handling of resource retrieval.
-	 * </ul>
-	 * 
-	 * @param classpathResourceFinder The resource finder class.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder classpathResourceFinder(Class<? extends ClasspathResourceFinder> classpathResourceFinder) {
-		return set(REST_classpathResourceFinder, classpathResourceFinder);
-	}
-	
-	/**
-	 * <b>Configuration property:</b>  Classpath resource finder. 
-	 * 
-	 * <p>
-	 * Used to retrieve localized files from the classpath.
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_classpathResourceFinder}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#classpathResourceFinder()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#classpathResourceFinder(Class)}
-	 * 			<li>{@link RestContextBuilder#classpathResourceFinder(ClasspathResourceFinder)}
-	 * 		</ul>
-	 * 	<li>
-	 * 		The default value is {@link ClasspathResourceFinderBasic} which provides basic support for finding localized
-	 * 		resources on the classpath and JVM working directory.
-	 * 		<br>The {@link ClasspathResourceFinderRecursive} is another option that also recursively searches for resources
-	 * 		up the parent class hierarchy.
-	 * 		<br>Each of these classes can be extended to provide customized handling of resource retrieval.
-	 * </ul>
-	 * 
-	 * @param classpathResourceFinder The resource finder instance.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder classpathResourceFinder(ClasspathResourceFinder classpathResourceFinder) {
-		return set(REST_classpathResourceFinder, classpathResourceFinder);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  Use classpath resource caching. 
-	 *
-	 * <p>
-	 * When enabled, resources retrieved via {@link RestContext#getClasspathResource(String, Locale)} (and related 
-	 * methods) will be cached in memory to speed subsequent lookups.
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_useClasspathResourceCaching}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link RestResource#useClasspathResourceCaching()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#useClasspathResourceCaching(boolean)}
-	 * 		</ul>
-	 * </ul>
-	 * 
-	 * @param value The new value.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder useClasspathResourceCaching(boolean value) {
-		return set(REST_useClasspathResourceCaching, value);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  HTML Widgets. 
-	 *
-	 * <p>
-	 * Defines widgets that can be used in conjunction with string variables of the form <js>"$W{name}"</js>to quickly
-	 * generate arbitrary replacement text.
-	 * 
-	 * Widgets resolve the following variables:
-	 * <ul>
-	 * 	<li><js>"$W{name}"</js> - Contents returned by {@link Widget#getHtml(RestRequest)}.
-	 * 	<li><js>"$W{name.script}"</js> - Contents returned by {@link Widget#getScript(RestRequest)}.
-	 * 		<br>The script contents are automatically inserted into the <xt>&lt;head/script&gt;</xt> section
-	 * 			 in the HTML page.
-	 * 	<li><js>"$W{name.style}"</js> - Contents returned by {@link Widget#getStyle(RestRequest)}.
-	 * 		<br>The styles contents are automatically inserted into the <xt>&lt;head/style&gt;</xt> section
-	 * 			 in the HTML page.
-	 * </ul>
-	 *
-	 * <p>
-	 * The following examples shows how to associate a widget with a REST method and then have it rendered in the links
-	 * and aside section of the page:
-	 *
-	 * <p class='bcode'>
-	 * 	<ja>@RestMethod</ja>(
-	 * 		widgets={
-	 * 			MyWidget.<jk>class</jk>
-	 * 		}
-	 * 		htmldoc=<ja>@HtmlDoc</ja>(
-	 * 			navlinks={
-	 * 				<js>"$W{MyWidget}"</js>
-	 * 			},
-	 * 			aside={
-	 * 				<js>"Check out this widget:  $W{MyWidget}"</js>
-	 * 			}
-	 * 		)
-	 * 	)
-	 * </p>
-	 * 
-	 * <h6 class='topic'>Notes:</h6>
-	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_widgets}
-	 * 	<li>Annotations: 
-	 * 		<ul>
-	 * 			<li>{@link HtmlDoc#widgets()} 
-	 * 		</ul>
-	 * 	<li>Methods: 
-	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#widgets(Class...)}
-	 * 			<li>{@link RestContextBuilder#widgets(Widget...)}
-	 * 			<li>{@link RestContextBuilder#widgets(boolean,Widget...)}
-	 * 		</ul>
-	 * 	<li>Widgets are inherited from parent to child, but can be overridden by reusing the widget name.
-	 * 	<li>Values are appended to the existing list.
-	 * </ul>
-	 *
-	 * @param values The widget classes to add.
-	 * @return This object (for method chaining).
-	 */
-	@SuppressWarnings("unchecked")
-	public RestContextBuilder widgets(Class<? extends Widget>...values) {
-		return addTo(REST_widgets, values);
-	}
-	
-	/**
-	 * <b>Configuration property:</b>  HTML Widgets. 
-	 * 
-	 * <p>
-	 * Same as {@link #widgets(Class...)} but allows you to pass in pre-instantiated Widget objects.
-	 * 
-	 * <p>
-	 * Values are appended to the existing list.
-	 *
-	 * @param values The widget objects to add.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder widgets(Widget...values) {
-		return addTo(REST_widgets, values);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  HTML Widgets. 
-	 * 
-	 * <p>
-	 * Same as {@link #widgets(Class...)} but allows you to pass in pre-instantiated Widget objects.
-	 * 
-	 * <p>
-	 * Values are appended to the existing list.
-	 * 
-	 * @param append 
-	 * 	If <jk>true</jk>, appends to the existing list of widgets.
-	 * 	<br>Otherwise, replaces the previous list.
-	 * @param values The widget objects to add.
-	 * @return This object (for method chaining).
-	 */
-	public RestContextBuilder widgets(boolean append, Widget...values) {
-		return set(append, REST_widgets, values);
-	}
-
-	/**
-	 * <b>Configuration property:</b>  MIME types. 
-	 *
-	 * <p>
-	 * Defines MIME-type file type mappings.
-	 * 
-	 * <p>
-	 * Used for specifying the content type on file resources retrieved through the following methods:
-	 * <ul>
-	 * 	<li>{@link RestContext#resolveStaticFile(String)}
-	 * 	<li>{@link RestRequest#getClasspathReaderResource(String,boolean,MediaType)}
-	 * 	<li>{@link RestRequest#getClasspathReaderResource(String,boolean)}
-	 * 	<li>{@link RestRequest#getClasspathReaderResource(String)}
-	 * </ul>
-	 * 
-	 * <p>
-	 * This list appends to the existing list provided by {@link ExtendedMimetypesFileTypeMap}.
+	 * <b>Configuration property:</b>  Serializer listener.
 	 * 
-	 * <h6 class='topic'>Notes:</h6>
+	 * <p>
+	 * Specifies the serializer listener class to use for listening to non-fatal serialization errors.
+	 *
+	 * <p>
+	 * <h5 class='section'>Notes:</h5>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_mimeTypes}
-	 * 	<li>Annotations: 
+	 * 	<li>Property:  {@link Serializer#SERIALIZER_listener}
+	 * 	<li>Annotations:
 	 * 		<ul>
-	 * 			<li>{@link RestResource#mimeTypes()} 
+	 * 			<li>{@link RestResource#serializerListener()} 
 	 * 		</ul>
-	 * 	<li>Methods: 
+	 * 	<li>Methods:
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#mimeTypes(String...)}
+	 * 			<li>{@link RestContextBuilder#serializerListener(Class)} 
 	 * 		</ul>
-	 * 	<li>Values are .mime.types formatted entry string.
-	 * 		<br>Example: <js>"image/svg+xml svg"</js>
-	 * </ul>
+	 *	</ul>
 	 *
-	 * @param mimeTypes The MIME-types to add to this config.
+	 * @param listener The listener to add to this config.
 	 * @return This object (for method chaining).
 	 */
-	public RestContextBuilder mimeTypes(String...mimeTypes) {
-		return addTo(REST_mimeTypes, mimeTypes);
+	public RestContextBuilder serializerListener(Class<? extends SerializerListener> listener) {
+		return set(SERIALIZER_listener, listener);
 	}
 
 	/**
@@ -2362,8 +1889,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * <b>Configuration property:</b>  Serializers. 
 	 *
 	 * <p>
-	 * Same as {@link #serializers(Class...)} except allows you to overwrite the list of existing serializers instead
-	 * of appending.
+	 * Same as {@link #serializers(Class...)} except allows you to overwrite the previous value.
 	 * 
 	 * @param append
 	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
@@ -2378,7 +1904,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * <b>Configuration property:</b>  Serializers. 
 	 *
 	 * <p>
-	 * Same as {@link #serializers(Class...)} except allows you to pass in serializer instances.
+	 * Same as {@link #serializers(Class...)} except input is pre-constructed instances.
 	 * 
 	 * <p>
 	 * Serializer instances are considered set-in-stone and do NOT inherit properties and transforms defined on the
@@ -2395,8 +1921,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * <b>Configuration property:</b>  Serializers. 
 	 *
 	 * <p>
-	 * Same as {@link #serializers(Serializer...)} except allows you to overwrite the list of existing serializers instead
-	 * of appending.
+	 * Same as {@link #serializers(Serializer...)} except allows you to overwrite the previous value.
 	 * 
 	 * @param append
 	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
@@ -2408,336 +1933,464 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Parsers. 
+	 * <b>Configuration property:</b>  Static file response headers. 
 	 *
 	 * <p>
-	 * Adds class-level parsers to this resource.
+	 * Used to customize the headers on responses returned for statically-served files.
 	 * 
 	 * <h6 class='topic'>Notes:</h6>
 	 * <ul class='spaced-list'>
-	 * 	<li>Property:  {@link RestContext#REST_parsers}
+	 * 	<li>Property:  {@link RestContext#REST_staticFileResponseHeaders}
 	 * 	<li>Annotations: 
 	 * 		<ul>
-	 * 			<li>{@link RestResource#parsers()} 
-	 * 			<li>{@link RestMethod#parsers()} 
+	 * 			<li>{@link RestResource#staticFileResponseHeaders()} 
 	 * 		</ul>
 	 * 	<li>Methods: 
 	 * 		<ul>
-	 * 			<li>{@link RestContextBuilder#parsers(Class...)}
-	 * 			<li>{@link RestContextBuilder#parsers(boolean,Class...)}
-	 * 			<li>{@link RestContextBuilder#parsers(Parser...)}
-	 * 			<li>{@link RestContextBuilder#parsers(boolean,Parser...)}
+	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(boolean,Map)}
+	 * 			<li>{@link RestContextBuilder#staticFileResponseHeaders(String...)}
+	 * 			<li>{@link RestContextBuilder#staticFileResponseHeader(String,String)}
 	 * 		</ul>
-	 * 	<li>When defined as a class, properties/transforms defined on the resource/method are inherited.
-	 * 	<li>When defined as an instance, properties/transforms defined on the resource/method are NOT inherited.
-	 * 	<li>Values are added AFTER those found in the annotation and therefore take precedence over those defined via the
-	 * 		annotation.
 	 * </ul>
+	 * 
+	 * @param append
+	 * 	If <jk>true</jk>, append to the existing list, otherwise overwrite the previous value. 
+	 * @param headers The headers to add to this list.
+	 * @return This object (for method chaining).
+	 */
+	public RestContextBuilder staticFileResponseHeaders(boolean append, Map<String,String> headers) {
+		return set(append, REST_staticFileResponseHeaders, headers);
+	}
+
+	/**
+	 * <b>Configuration property:</b>  Static file response headers. 
 	 *
-	 * @param parsers The parser classes to add to this config.
+	 * <p>
+	 * Same as {@link #staticFileResponseHeaders(boolean, Map)} with append=<jk>true</jk> except headers are strings 
+	 * composed of key/value pairs.
+	 * 
+	 * @param headers The headers in the format <js>"Header-Name: header-value"</js>.
 	 * @return This object (for method chaining).
+	 * @throws RestServletException If malformed header is found.
 	 */
-	public RestContextBuilder parsers(Class<?>...parsers) {
-		return addTo(REST_parsers, parsers);
+	public RestContextBuilder staticFileResponseHeaders(String...headers) throws RestServletException {
+		for (String header : headers) {
+			String[] h = RestUtils.parseHeader(header);
+			if (h == null)
+				throw new RestServletException("Invalid static file response header specified: ''{0}''.  Must be in the format: ''Header-Name: header-value''", header);
+			staticFileResponseHeader(h[0], h[1]);
+		}
+		return this;
 	}
 
 	/**
-	 * <b>Configuration property:</b>  Parsers. 
+	 * <b>Configuration property:</b>  Static file response headers. 
 	 *
 	 * <p>
-	 * Same as {@

<TRUNCATED>


[2/3] juneau git commit: RestContext refactoring.

Posted by ja...@apache.org.
RestContext refactoring.

Project: http://git-wip-us.apache.org/repos/asf/juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/juneau/commit/76b634af
Tree: http://git-wip-us.apache.org/repos/asf/juneau/tree/76b634af
Diff: http://git-wip-us.apache.org/repos/asf/juneau/diff/76b634af

Branch: refs/heads/master
Commit: 76b634af0d7193f43f253f24c392db9eb8c5b228
Parents: b766e1d
Author: JamesBognar <ja...@apache.org>
Authored: Fri Jan 5 22:27:49 2018 -0500
Committer: JamesBognar <ja...@apache.org>
Committed: Fri Jan 5 22:27:49 2018 -0500

----------------------------------------------------------------------
 .../apache/juneau/rest/RestContextBuilder.java  | 2193 ++++++++----------
 1 file changed, 923 insertions(+), 1270 deletions(-)
----------------------------------------------------------------------



[3/3] juneau git commit: RestContext refactoring.

Posted by ja...@apache.org.
RestContext refactoring.

Project: http://git-wip-us.apache.org/repos/asf/juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/juneau/commit/96fae4f9
Tree: http://git-wip-us.apache.org/repos/asf/juneau/tree/96fae4f9
Diff: http://git-wip-us.apache.org/repos/asf/juneau/diff/96fae4f9

Branch: refs/heads/master
Commit: 96fae4f9896b70ca2cea9bd4591f39736b96ecb5
Parents: 76b634a
Author: JamesBognar <ja...@apache.org>
Authored: Fri Jan 5 22:39:31 2018 -0500
Committer: JamesBognar <ja...@apache.org>
Committed: Fri Jan 5 22:39:31 2018 -0500

----------------------------------------------------------------------
 .../java/org/apache/juneau/rest/CallMethod.java | 921 -------------------
 .../java/org/apache/juneau/rest/CallRouter.java | 100 --
 .../org/apache/juneau/rest/RestCallHandler.java |  12 +-
 .../org/apache/juneau/rest/RestCallRouter.java  | 100 ++
 .../org/apache/juneau/rest/RestContext.java     |  24 +-
 .../apache/juneau/rest/RestInfoProvider.java    |   6 +-
 .../org/apache/juneau/rest/RestJavaMethod.java  | 921 +++++++++++++++++++
 7 files changed, 1042 insertions(+), 1042 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
deleted file mode 100644
index d33b60b..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
+++ /dev/null
@@ -1,921 +0,0 @@
-// ***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
-// * with the License.  You may obtain a copy of the License at                                                              *
-// *                                                                                                                         *
-// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
-// *                                                                                                                         *
-// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
-// * specific language governing permissions and limitations under the License.                                              *
-// ***************************************************************************************************************************
-package org.apache.juneau.rest;
-
-import static javax.servlet.http.HttpServletResponse.*;
-import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
-import static org.apache.juneau.internal.ClassUtils.*;
-import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.internal.Utils.*;
-import static org.apache.juneau.BeanContext.*;
-import static org.apache.juneau.rest.RestContext.*;
-
-import java.lang.annotation.*;
-import java.lang.reflect.*;
-import java.util.*;
-
-import javax.servlet.http.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.encoders.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.httppart.*;
-import org.apache.juneau.httppart.HttpPartParser;
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.widget.*;
-import org.apache.juneau.serializer.*;
-import org.apache.juneau.svl.*;
-import org.apache.juneau.utils.*;
-
-/**
- * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
- */
-class CallMethod implements Comparable<CallMethod>  {
-	private final java.lang.reflect.Method method;
-	private final String httpMethod;
-	private final UrlPathPattern pathPattern;
-	private final RestParam[] params;
-	private final RestGuard[] guards;
-	private final RestMatcher[] optionalMatchers;
-	private final RestMatcher[] requiredMatchers;
-	private final RestConverter[] converters;
-	private final SerializerGroup serializers;
-	private final ParserGroup parsers;
-	private final EncoderGroup encoders;
-	private final HttpPartParser partParser;
-	private final HttpPartSerializer partSerializer;
-	private final ObjectMap properties;
-	private final Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
-	private final String defaultCharset;
-	private final long maxInput;
-	private final boolean deprecated;
-	private final String description, tags, summary, externalDocs;
-	private final Integer priority;
-	private final org.apache.juneau.rest.annotation.Parameter[] parameters;
-	private final Response[] responses;
-	private final RestContext context;
-	private final BeanContext beanContext;
-	private final Map<String,Widget> widgets;
-	private final List<MediaType> supportedAcceptTypes, supportedContentTypes;
-
-	CallMethod(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
-		Builder b = new Builder(servlet, method, context);
-		this.context = context;
-		this.method = method;
-		this.httpMethod = b.httpMethod;
-		this.pathPattern = b.pathPattern;
-		this.params = b.params;
-		this.guards = b.guards;
-		this.optionalMatchers = b.optionalMatchers;
-		this.requiredMatchers = b.requiredMatchers;
-		this.converters = b.converters;
-		this.serializers = b.serializers;
-		this.parsers = b.parsers;
-		this.encoders = b.encoders;
-		this.partParser = b.partParser;
-		this.partSerializer = b.partSerializer;
-		this.beanContext = b.beanContext;
-		this.properties = b.properties;
-		this.defaultRequestHeaders = b.defaultRequestHeaders;
-		this.defaultQuery = b.defaultQuery;
-		this.defaultFormData = b.defaultFormData;
-		this.defaultCharset = b.defaultCharset;
-		this.maxInput = b.maxInput;
-		this.deprecated = b.deprecated;
-		this.description = b.description;
-		this.tags = b.tags;
-		this.summary = b.summary;
-		this.externalDocs = b.externalDocs;
-		this.priority = b.priority;
-		this.parameters = b.parameters;
-		this.responses = b.responses;
-		this.supportedAcceptTypes = b.supportedAcceptTypes;
-		this.supportedContentTypes = b.supportedContentTypes;
-		this.widgets = Collections.unmodifiableMap(b.widgets);
-	}
-
-	private static final class Builder  {
-		String httpMethod, defaultCharset, description, tags, summary, externalDocs;
-		UrlPathPattern pathPattern;
-		RestParam[] params;
-		RestGuard[] guards;
-		RestMatcher[] optionalMatchers, requiredMatchers;
-		RestConverter[] converters;
-		SerializerGroup serializers;
-		ParserGroup parsers;
-		EncoderGroup encoders;
-		HttpPartParser partParser;
-		HttpPartSerializer partSerializer;
-		BeanContext beanContext;
-		ObjectMap properties;
-		Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
-		boolean deprecated;
-		long maxInput;
-		Integer priority;
-		org.apache.juneau.rest.annotation.Parameter[] parameters;
-		Response[] responses;
-		Map<String,Widget> widgets;
-		List<MediaType> supportedAcceptTypes, supportedContentTypes;
-
-		Builder(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
-			String sig = method.getDeclaringClass().getName() + '.' + method.getName();
-
-			try {
-
-				RestMethod m = method.getAnnotation(RestMethod.class);
-				if (m == null)
-					throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
-
-				if (! m.description().isEmpty())
-					description = m.description();
-				MethodSwagger sm = m.swagger();
-				if (! sm.tags().isEmpty())
-					tags = sm.tags();
-				if (! m.summary().isEmpty())
-					summary = m.summary();
-				if (! sm.externalDocs().isEmpty())
-					externalDocs = sm.externalDocs();
-				deprecated = sm.deprecated();
-				parameters = sm.parameters();
-				responses = sm.responses();
-				serializers = context.getSerializers();
-				parsers = context.getParsers();
-				partSerializer = context.getPartSerializer();
-				partParser = context.getPartParser();
-				beanContext = context.getBeanContext();
-				encoders = context.getEncoders();
-				properties = new ObjectMap().setInner(context.getProperties());
-				defaultCharset = context.getDefaultCharset();
-				maxInput = context.getMaxInput();
-
-				if (! m.defaultCharset().isEmpty())
-					defaultCharset = context.getVarResolver().resolve(m.defaultCharset());
-				if (! m.maxInput().isEmpty())
-					maxInput = StringUtils.parseLongWithSuffix(context.getVarResolver().resolve(m.maxInput()));
-
-				HtmlDocBuilder hdb = new HtmlDocBuilder(properties);
-
-				HtmlDoc hd = m.htmldoc();
-				hdb.process(hd);
-
-				widgets = new HashMap<>(context.getWidgets());
-				for (Class<? extends Widget> wc : hd.widgets()) {
-					Widget w = beanContext.newInstance(Widget.class, wc);
-					widgets.put(w.getName(), w);
-					hdb.script("INHERIT", "$W{"+w.getName()+".script}");
-					hdb.style("INHERIT", "$W{"+w.getName()+".style}");
-				}
-
-				ASet<String> inherit = new ASet<String>().appendAll(StringUtils.split(m.inherit()));
-				if (inherit.contains("*")) 
-					inherit.appendAll("SERIALIZERS","PARSERS","TRANSFORMS","PROPERTIES","ENCODERS");
-
-				SerializerGroupBuilder sgb = null;
-				ParserGroupBuilder pgb = null;
-				ParserBuilder uepb = null;
-				BeanContextBuilder bcb = null;
-				PropertyStore cps = context.getPropertyStore();
-
-				if (m.serializers().length > 0 || m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0
-						|| m.beanFilters().length > 0 || m.pojoSwaps().length > 0 || m.bpi().length > 0
-						|| m.bpx().length > 0) {
-					sgb = SerializerGroup.create();
-					pgb = ParserGroup.create();
-					uepb = Parser.create();
-					bcb = beanContext.builder();
-
-					if (inherit.contains("SERIALIZERS") || m.serializers().length == 0)
-						sgb.append(cps.getArrayProperty(REST_serializers, Object.class));
-
-					if (inherit.contains("PARSERS") || m.parsers().length == 0)
-						pgb.append(cps.getArrayProperty(REST_parsers, Object.class));
-				}
-
-				httpMethod = m.name().toUpperCase(Locale.ENGLISH);
-				if (httpMethod.equals("") && method.getName().startsWith("do"))
-					httpMethod = method.getName().substring(2).toUpperCase(Locale.ENGLISH);
-				if (httpMethod.equals(""))
-					httpMethod = "GET";
-				if (httpMethod.equals("METHOD"))
-					httpMethod = "*";
-
-				priority = m.priority();
-
-				String p = m.path();
-				converters = new RestConverter[m.converters().length];
-				for (int i = 0; i < converters.length; i++)
-					converters[i] = beanContext.newInstance(RestConverter.class, m.converters()[i]);
-
-				guards = new RestGuard[m.guards().length];
-				for (int i = 0; i < guards.length; i++)
-					guards[i] = beanContext.newInstance(RestGuard.class, m.guards()[i]);
-
-				List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>();
-				for (int i = 0; i < m.matchers().length; i++) {
-					Class<? extends RestMatcher> c = m.matchers()[i];
-					RestMatcher matcher = beanContext.newInstance(RestMatcher.class, c, true, servlet, method);
-					if (matcher.mustMatch())
-						requiredMatchers.add(matcher);
-					else
-						optionalMatchers.add(matcher);
-				}
-				if (! m.clientVersion().isEmpty())
-					requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), method));
-
-				this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
-				this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
-
-				PropertyStore ps = context.getPropertyStore();
-				if (! inherit.contains("TRANSFORMS"))
-					ps = ps.builder().set(BEAN_beanFilters, null).set(BEAN_pojoSwaps, null).build();
-				
-				if (sgb != null) {
-					sgb.append(m.serializers());
-				
-					if (! inherit.contains("PROPERTIES"))
-						sgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
-					else
-						sgb.apply(ps);
-					for (Property p1 : m.properties())
-						sgb.set(p1.name(), p1.value());
-					for (String p1 : m.flags())
-						sgb.set(p1, true);
-					if (m.bpi().length > 0) {
-						Map<String,String> bpiMap = new LinkedHashMap<>();
-						for (String s : m.bpi()) {
-							for (String s2 : split(s, ';')) {
-								int i = s2.indexOf(':');
-								if (i == -1)
-									throw new RestServletException(
-										"Invalid format for @RestMethod.bpi() on method ''{0}''.  Must be in the format \"ClassName: comma-delimited-tokens\".  \nValue: {1}", sig, s);
-								bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
-							}
-						}
-						sgb.includeProperties(bpiMap);
-					}
-					if (m.bpx().length > 0) {
-						Map<String,String> bpxMap = new LinkedHashMap<>();
-						for (String s : m.bpx()) {
-							for (String s2 : split(s, ';')) {
-								int i = s2.indexOf(':');
-								if (i == -1)
-									throw new RestServletException(
-										"Invalid format for @RestMethod.bpx() on method ''{0}''.  Must be in the format \"ClassName: comma-delimited-tokens\".  \nValue: {1}", sig, s);
-								bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
-							}
-						}
-						sgb.excludeProperties(bpxMap);
-					}
-					sgb.beanFilters(m.beanFilters());
-					sgb.pojoSwaps(m.pojoSwaps());
-				}
-
-				if (pgb != null) {
-					pgb.append(m.parsers());
-					if (! inherit.contains("PROPERTIES"))
-						pgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
-					else
-						pgb.apply(ps);
-					for (Property p1 : m.properties())
-						pgb.set(p1.name(), p1.value());
-					for (String p1 : m.flags())
-						pgb.set(p1, true);
-					pgb.beanFilters(m.beanFilters());
-					pgb.pojoSwaps(m.pojoSwaps());
-				}
-
-				if (uepb != null) {
-					uepb.apply(ps);
-					for (Property p1 : m.properties())
-						uepb.set(p1.name(), p1.value());
-					for (String p1 : m.flags())
-						uepb.set(p1, true);
-					uepb.beanFilters(m.beanFilters());
-					uepb.pojoSwaps(m.pojoSwaps());
-				}
-				
-				if (bcb != null) {
-					bcb.apply(ps);
-					for (Property p1 : m.properties())
-						bcb.set(p1.name(), p1.value());
-					for (String p1 : m.flags())
-						bcb.set(p1, true);
-					bcb.beanFilters(m.beanFilters());
-					bcb.pojoSwaps(m.pojoSwaps());
-				}
-				
-				if (m.properties().length > 0 || m.flags().length > 0) {
-					properties = new ObjectMap().setInner(properties);
-					for (Property p1 : m.properties())
-						properties.put(p1.name(), p1.value());
-					for (String p1 : m.flags())
-						properties.put(p1, true);
-				}
-
-				if (m.encoders().length > 0) {
-					EncoderGroupBuilder g = EncoderGroup.create().append(IdentityEncoder.INSTANCE);
-					if (inherit.contains("ENCODERS"))
-						g.append(encoders);
-
-					for (Class<? extends Encoder> c : m.encoders()) {
-						try {
-							g.append(c);
-						} catch (Exception e) {
-							throw new RestServletException(
-								"Exception occurred while trying to instantiate Encoder on method ''{0}'': ''{1}''", sig, c.getSimpleName()).initCause(e);
-						}
-					}
-					encoders = g.build();
-				}
-
-				defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-				for (String s : m.defaultRequestHeaders()) {
-					String[] h = RestUtils.parseKeyValuePair(s);
-					if (h == null)
-						throw new RestServletException(
-							"Invalid default request header specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
-					defaultRequestHeaders.put(h[0], h[1]);
-				}
-
-				defaultQuery = new LinkedHashMap<>();
-				for (String s : m.defaultQuery()) {
-					String[] h = RestUtils.parseKeyValuePair(s);
-					if (h == null)
-						throw new RestServletException(
-							"Invalid default query parameter specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
-					defaultQuery.put(h[0], h[1]);
-				}
-
-				defaultFormData = new LinkedHashMap<>();
-				for (String s : m.defaultFormData()) {
-					String[] h = RestUtils.parseKeyValuePair(s);
-					if (h == null)
-						throw new RestServletException(
-							"Invalid default form data parameter specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
-					defaultFormData.put(h[0], h[1]);
-				}
-
-				Type[] pt = method.getGenericParameterTypes();
-				Annotation[][] pa = method.getParameterAnnotations();
-				for (int i = 0; i < pt.length; i++) {
-					for (Annotation a : pa[i]) {
-						if (a instanceof Header) {
-							Header h = (Header)a;
-							if (! h.def().isEmpty())
-								defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), h.def());
-						} else if (a instanceof Query) {
-							Query q = (Query)a;
-							if (! q.def().isEmpty())
-								defaultQuery.put(firstNonEmpty(q.name(), q.value()), q.def());
-						} else if (a instanceof FormData) {
-							FormData f = (FormData)a;
-							if (! f.def().isEmpty())
-								defaultFormData.put(firstNonEmpty(f.name(), f.value()), f.def());
-						}
-					}
-				}
-
-				pathPattern = new UrlPathPattern(p);
-
-				if (sgb != null) 
-					serializers = sgb.build();
-				if (pgb != null)
-					parsers = pgb.build();
-				if (uepb != null && partParser instanceof Parser) {
-					Parser pp = (Parser)partParser;
-					partParser = (HttpPartParser)pp.builder().apply(uepb.getPropertyStore()).build();
-				}
-				if (bcb != null)
-					beanContext = bcb.build();
-
-				supportedAcceptTypes = 
-					m.supportedAcceptTypes().length > 0 
-					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedAcceptTypes())))) 
-					: serializers.getSupportedMediaTypes();
-				supportedContentTypes =
-					m.supportedContentTypes().length > 0 
-					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedContentTypes())))) 
-					: parsers.getSupportedMediaTypes();
-					
-				params = context.findParams(method, pathPattern, false);
-
-				// Need this to access methods in anonymous inner classes.
-				method.setAccessible(true);
-			} catch (RestServletException e) {
-				throw e;
-			} catch (Exception e) {
-				throw new RestServletException("Exception occurred while initializing method ''{0}''", sig).initCause(e);
-			}
-		}
-	}
-
-	/**
-	 * Returns <jk>true</jk> if this Java method has any guards or matchers.
-	 */
-	boolean hasGuardsOrMatchers() {
-		return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0);
-	}
-
-	/**
-	 * Returns the HTTP method name (e.g. <js>"GET"</js>).
-	 */
-	String getHttpMethod() {
-		return httpMethod;
-	}
-
-	/**
-	 * Returns the path pattern for this method.
-	 */
-	String getPathPattern() {
-		return pathPattern.toString();
-	}
-
-	/**
-	 * Returns the localized Swagger for this Java method.
-	 */
-	Operation getSwaggerOperation(RestRequest req) throws ParseException {
-		Operation o = operation()
-			.operationId(method.getName())
-			.description(getDescription(req))
-			.tags(getTags(req))
-			.summary(getSummary(req))
-			.externalDocs(getExternalDocs(req))
-			.parameters(getParameters(req))
-			.responses(getResponses(req));
-
-		if (isDeprecated())
-			o.deprecated(true);
-
-		if (! parsers.getSupportedMediaTypes().equals(context.getParsers().getSupportedMediaTypes()))
-			o.consumes(parsers.getSupportedMediaTypes());
-
-		if (! serializers.getSupportedMediaTypes().equals(context.getSerializers().getSupportedMediaTypes()))
-			o.produces(serializers.getSupportedMediaTypes());
-
-		return o;
-	}
-
-	private Operation getSwaggerOperationFromFile(RestRequest req) {
-		Swagger s = req.getSwaggerFromFile();
-		if (s != null && s.getPaths() != null && s.getPaths().get(pathPattern.getPatternString()) != null)
-			return s.getPaths().get(pathPattern.getPatternString()).get(httpMethod);
-		return null;
-	}
-
-	/**
-	 * Returns the localized summary for this Java method.
-	 */
-	String getSummary(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		if (summary != null)
-			return vr.resolve(summary);
-		String summary = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".summary");
-		if (summary != null)
-			return vr.resolve(summary);
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null)
-			return o.getSummary();
-		return null;
-	}
-
-	/**
-	 * Returns the localized description for this Java method.
-	 */
-	String getDescription(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		if (description != null)
-			return vr.resolve(description);
-		String description = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".description");
-		if (description != null)
-			return vr.resolve(description);
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null)
-			return o.getDescription();
-		return null;
-	}
-
-	/**
-	 * Returns the localized Swagger tags for this Java method.
-	 */
-	private List<String> getTags(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		try {
-			if (tags != null)
-				return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
-			String tags = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".tags");
-			if (tags != null)
-				return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
-			Operation o = getSwaggerOperationFromFile(req);
-			if (o != null)
-				return o.getTags();
-			return null;
-		} catch (Exception e) {
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
-		}
-	}
-
-	/**
-	 * Returns the localized Swagger external docs for this Java method.
-	 */
-	private ExternalDocumentation getExternalDocs(RestRequest req) {
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		try {
-			if (externalDocs != null)
-				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
-			String externalDocs = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".externalDocs");
-			if (externalDocs != null)
-				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
-			Operation o = getSwaggerOperationFromFile(req);
-			if (o != null)
-				return o.getExternalDocs();
-			return null;
-		} catch (Exception e) {
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
-		}
-	}
-
-	/**
-	 * Returns the Swagger deprecated flag for this Java method.
-	 */
-	private boolean isDeprecated() {
-		return deprecated;
-	}
-
-	/**
-	 * Returns the localized Swagger parameter information for this Java method.
-	 */
-	private List<ParameterInfo> getParameters(RestRequest req) throws ParseException {
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null && o.getParameters() != null)
-			return o.getParameters();
-
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		Map<String,ParameterInfo> m = new TreeMap<>();
-
-		// First parse @RestMethod.parameters() annotation.
-		for (org.apache.juneau.rest.annotation.Parameter v : parameters) {
-			String in = vr.resolve(v.in());
-			ParameterInfo p = parameterInfo(in, vr.resolve(v.name()));
-
-			if (! v.description().isEmpty())
-				p.description(vr.resolve(v.description()));
-			if (v.required())
-				p.required(v.required());
-
-			if ("body".equals(in)) {
-				if (! v.schema().isEmpty())
-					p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
-			} else {
-				if (v.allowEmptyValue())
-					p.allowEmptyValue(v.allowEmptyValue());
-				if (! v.collectionFormat().isEmpty())
-					p.collectionFormat(vr.resolve(v.collectionFormat()));
-				if (! v._default().isEmpty())
-					p._default(vr.resolve(v._default()));
-				if (! v.format().isEmpty())
-					p.format(vr.resolve(v.format()));
-				if (! v.items().isEmpty())
-					p.items(jp.parse(vr.resolve(v.items()), Items.class));
-				p.type(vr.resolve(v.type()));
-			}
-			m.put(p.getIn() + '.' + p.getName(), p);
-		}
-
-		// Next, look in resource bundle.
-		String prefix = method.getName() + ".req";
-		for (String key : context.getMessages().keySet(prefix)) {
-			if (key.length() > prefix.length()) {
-				String value = vr.resolve(context.getMessages().getString(key));
-				String[] parts = key.substring(prefix.length() + 1).split("\\.");
-				String in = parts[0], name, field;
-				boolean isBody = "body".equals(in);
-				if (parts.length == (isBody ? 2 : 3)) {
-					if ("body".equals(in)) {
-						name = null;
-						field = parts[1];
-					} else {
-						name = parts[1];
-						field = parts[2];
-					}
-					String k2 = in + '.' + name;
-					ParameterInfo p = m.get(k2);
-					if (p == null) {
-						p = parameterInfoStrict(in, name);
-						m.put(k2, p);
-					}
-
-					if (field.equals("description"))
-						p.description(value);
-					else if (field.equals("required"))
-						p.required(Boolean.valueOf(value));
-
-					if ("body".equals(in)) {
-						if (field.equals("schema"))
-							p.schema(jp.parse(value, SchemaInfo.class));
-					} else {
-						if (field.equals("allowEmptyValue"))
-							p.allowEmptyValue(Boolean.valueOf(value));
-						else if (field.equals("collectionFormat"))
-							p.collectionFormat(value);
-						else if (field.equals("default"))
-							p._default(value);
-						else if (field.equals("format"))
-							p.format(value);
-						else if (field.equals("items"))
-							p.items(jp.parse(value, Items.class));
-						else if (field.equals("type"))
-							p.type(value);
-					}
-				} else {
-					System.err.println("Unknown bundle key '"+key+"'");
-				}
-			}
-		}
-
-		// Finally, look for parameters defined on method.
-		for (RestParam mp : this.params) {
-			RestParamType in = mp.getParamType();
-			if (in != RestParamType.OTHER) {
-				String k2 = in.toString() + '.' + (in == RestParamType.BODY ? null : mp.getName());
-				ParameterInfo p = m.get(k2);
-				if (p == null) {
-					p = parameterInfoStrict(in.toString(), mp.getName());
-					m.put(k2, p);
-				}
-			}
-		}
-
-		if (m.isEmpty())
-			return null;
-		return new ArrayList<>(m.values());
-	}
-
-	/**
-	 * Returns the localized Swagger response information about this Java method.
-	 */
-	private Map<Integer,ResponseInfo> getResponses(RestRequest req) throws ParseException {
-		Operation o = getSwaggerOperationFromFile(req);
-		if (o != null && o.getResponses() != null)
-			return o.getResponses();
-
-		VarResolverSession vr = req.getVarResolverSession();
-		JsonParser jp = JsonParser.DEFAULT;
-		Map<Integer,ResponseInfo> m = new TreeMap<>();
-		Map<String,HeaderInfo> m2 = new TreeMap<>();
-
-		// First parse @RestMethod.parameters() annotation.
-		for (Response r : responses) {
-			int httpCode = r.value();
-			String description = r.description().isEmpty() ? RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
-			ResponseInfo r2 = responseInfo(description);
-
-			if (r.headers().length > 0) {
-				for (org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
-					HeaderInfo h = headerInfoStrict(vr.resolve(v.type()));
-					if (! v.collectionFormat().isEmpty())
-						h.collectionFormat(vr.resolve(v.collectionFormat()));
-					if (! v._default().isEmpty())
-						h._default(vr.resolve(v._default()));
-					if (! v.description().isEmpty())
-						h.description(vr.resolve(v.description()));
-					if (! v.format().isEmpty())
-						h.format(vr.resolve(v.format()));
-					if (! v.items().isEmpty())
-						h.items(jp.parse(vr.resolve(v.items()), Items.class));
-					r2.header(v.name(), h);
-					m2.put(httpCode + '.' + v.name(), h);
-				}
-			}
-			m.put(httpCode, r2);
-		}
-
-		// Next, look in resource bundle.
-		String prefix = method.getName() + ".res";
-		for (String key : context.getMessages().keySet(prefix)) {
-			if (key.length() > prefix.length()) {
-				String value = vr.resolve(context.getMessages().getString(key));
-				String[] parts = key.substring(prefix.length() + 1).split("\\.");
-				int httpCode = Integer.parseInt(parts[0]);
-				ResponseInfo r2 = m.get(httpCode);
-				if (r2 == null) {
-					r2 = responseInfo(null);
-					m.put(httpCode, r2);
-				}
-
-				String name = parts.length > 1 ? parts[1] : "";
-
-				if ("header".equals(name) && parts.length > 3) {
-					String headerName = parts[2];
-					String field = parts[3];
-
-					String k2 = httpCode + '.' + headerName;
-					HeaderInfo h = m2.get(k2);
-					if (h == null) {
-						h = headerInfoStrict("string");
-						m2.put(k2, h);
-						r2.header(name, h);
-					}
-					if (field.equals("collectionFormat"))
-						h.collectionFormat(value);
-					else if (field.equals("default"))
-						h._default(value);
-					else if (field.equals("description"))
-						h.description(value);
-					else if (field.equals("format"))
-						h.format(value);
-					else if (field.equals("items"))
-						h.items(jp.parse(value, Items.class));
-					else if (field.equals("type"))
-						h.type(value);
-
-				} else if ("description".equals(name)) {
-					r2.description(value);
-				} else if ("schema".equals(name)) {
-					r2.schema(jp.parse(value, SchemaInfo.class));
-				} else if ("examples".equals(name)) {
-					r2.examples(jp.parse(value, TreeMap.class));
-				} else {
-					System.err.println("Unknown bundle key '"+key+"'");
-				}
-			}
-		}
-
-		return m.isEmpty() ? null : m;
-	}
-
-	/**
-	 * Returns <jk>true</jk> if the specified request object can call this method.
-	 */
-	boolean isRequestAllowed(RestRequest req) {
-		for (RestGuard guard : guards) {
-			req.setJavaMethod(method);
-			if (! guard.isRequestAllowed(req))
-				return false;
-		}
-		return true;
-	}
-
-	/**
-	 * Workhorse method.
-	 *
-	 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
-	 * @return The HTTP response code.
-	 */
-	int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
-
-		String[] patternVals = pathPattern.match(pathInfo);
-		if (patternVals == null)
-			return SC_NOT_FOUND;
-
-		String remainder = null;
-		if (patternVals.length > pathPattern.getVars().length)
-			remainder = patternVals[pathPattern.getVars().length];
-		for (int i = 0; i < pathPattern.getVars().length; i++)
-			req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]);
-		req.getPathMatch().setRemainder(remainder);
-
-		ObjectMap requestProperties = new ResolvingObjectMap(req.getVarResolverSession()).setInner(properties);
-
-		req.init(method, requestProperties, defaultRequestHeaders, defaultQuery, defaultFormData, defaultCharset,
-			maxInput, serializers, parsers, partParser, beanContext, encoders, widgets, supportedAcceptTypes, supportedContentTypes);
-		res.init(requestProperties, defaultCharset, serializers, partSerializer, encoders);
-
-		// Class-level guards
-		for (RestGuard guard : context.getGuards())
-			if (! guard.guard(req, res))
-				return SC_UNAUTHORIZED;
-
-		// If the method implements matchers, test them.
-		for (RestMatcher m : requiredMatchers)
-			if (! m.matches(req))
-				return SC_PRECONDITION_FAILED;
-		if (optionalMatchers.length > 0) {
-			boolean matches = false;
-			for (RestMatcher m : optionalMatchers)
-				matches |= m.matches(req);
-			if (! matches)
-				return SC_PRECONDITION_FAILED;
-		}
-
-		context.preCall(req, res);
-
-		Object[] args = new Object[params.length];
-		for (int i = 0; i < params.length; i++) {
-			try {
-				args[i] = params[i].resolve(req, res);
-			} catch (RestException e) {
-				throw e;
-			} catch (Exception e) {
-				throw new RestException(SC_BAD_REQUEST,
-					"Invalid data conversion.  Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
-					params[i].getParamType().name(), params[i].getName(), params[i].getType(), method.getDeclaringClass().getName(), method.getName()
-				).initCause(e);
-			}
-		}
-
-		try {
-
-			for (RestGuard guard : guards)
-				if (! guard.guard(req, res))
-					return SC_OK;
-
-			Object output = method.invoke(context.getResource(), args);
-			if (! method.getReturnType().equals(Void.TYPE))
-				if (output != null || ! res.getOutputStreamCalled())
-					res.setOutput(output);
-
-			context.postCall(req, res);
-
-			if (res.hasOutput()) {
-				output = res.getOutput();
-				for (RestConverter converter : converters)
-					output = converter.convert(req, output, beanContext.getClassMetaForObject(output));
-				res.setOutput(output);
-			}
-		} catch (IllegalArgumentException e) {
-			throw new RestException(SC_BAD_REQUEST,
-				"Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}",
-				method.toString(), getReadableClassNames(args)
-			).initCause(e);
-		} catch (InvocationTargetException e) {
-			Throwable e2 = e.getTargetException();		// Get the throwable thrown from the doX() method.
-			if (e2 instanceof RestException)
-				throw (RestException)e2;
-			if (e2 instanceof ParseException)
-				throw new RestException(SC_BAD_REQUEST, e2);
-			if (e2 instanceof InvalidDataConversionException)
-				throw new RestException(SC_BAD_REQUEST, e2);
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e2);
-		} catch (RestException e) {
-			throw e;
-		} catch (Exception e) {
-			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
-		}
-		return SC_OK;
-	}
-
-	@Override /* Object */
-	public String toString() {
-		return "SimpleMethod: name=" + httpMethod + ", path=" + pathPattern.getPatternString();
-	}
-
-	/*
-	 * compareTo() method is used to keep SimpleMethods ordered in the CallRouter list.
-	 * It maintains the order in which matches are made during requests.
-	 */
-	@Override /* Comparable */
-	public int compareTo(CallMethod o) {
-		int c;
-
-		c = priority.compareTo(o.priority);
-		if (c != 0)
-			return c;
-
-		c = pathPattern.compareTo(o.pathPattern);
-		if (c != 0)
-			return c;
-
-		c = compare(o.requiredMatchers.length, requiredMatchers.length);
-		if (c != 0)
-			return c;
-
-		c = compare(o.optionalMatchers.length, optionalMatchers.length);
-		if (c != 0)
-			return c;
-
-		c = compare(o.guards.length, guards.length);
-		if (c != 0)
-			return c;
-
-		return 0;
-	}
-
-	@Override /* Object */
-	public boolean equals(Object o) {
-		if (! (o instanceof CallMethod))
-			return false;
-		return (compareTo((CallMethod)o) == 0);
-	}
-
-	@Override /* Object */
-	public int hashCode() {
-		return super.hashCode();
-	}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
deleted file mode 100644
index fc6c424..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// ***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
-// * with the License.  You may obtain a copy of the License at                                                              *
-// *                                                                                                                         *
-// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
-// *                                                                                                                         *
-// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
-// * specific language governing permissions and limitations under the License.                                              *
-// ***************************************************************************************************************************
-package org.apache.juneau.rest;
-
-import static javax.servlet.http.HttpServletResponse.*;
-
-import java.util.*;
-
-import javax.servlet.http.*;
-
-/**
- * Represents a group of CallMethods on a REST resource that handle the same HTTP Method name but with different
- * paths/matchers/guards/etc...
- *
- * <p>
- * Incoming requests for a particular HTTP method type (e.g. <js>"GET"</js>) are handed off to this class and then
- * dispatched to the appropriate CallMethod.
- */
-class CallRouter {
-	private final CallMethod[] callMethods;
-
-	CallRouter(CallMethod[] callMethods) {
-		this.callMethods = callMethods;
-	}
-
-	/**
-	 * Builder class.
-	 */
-	static final class Builder {
-		private List<CallMethod> childMethods = new ArrayList<>();
-		private Set<String> collisions = new HashSet<>();
-		private String httpMethodName;
-
-		Builder(String httpMethodName) {
-			this.httpMethodName = httpMethodName;
-		}
-
-		String getHttpMethodName() {
-			return httpMethodName;
-		}
-
-		Builder add(CallMethod m) throws RestServletException {
-			if (! m.hasGuardsOrMatchers()) {
-				String p = m.getHttpMethod() + ":" + m.getPathPattern();
-				if (collisions.contains(p))
-					throw new RestServletException("Duplicate Java methods assigned to the same method/pattern:  ''{0}''", p);
-				collisions.add(p);
-			}
-			childMethods.add(m);
-			return this;
-		}
-
-		CallRouter build() {
-			Collections.sort(childMethods);
-			return new CallRouter(childMethods.toArray(new CallMethod[childMethods.size()]));
-		}
-	}
-
-	/**
-	 * Workhorse method.
-	 *
-	 * <p>
-	 * Routes this request to one of the CallMethods.
-	 *
-	 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
-	 * @return The HTTP response code.
-	 */
-	int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
-		if (callMethods.length == 1)
-			return callMethods[0].invoke(pathInfo, req, res);
-
-		int maxRc = 0;
-		for (CallMethod m : callMethods) {
-			int rc = m.invoke(pathInfo, req, res);
-			if (rc == SC_OK)
-				return SC_OK;
-			maxRc = Math.max(maxRc, rc);
-		}
-		return maxRc;
-	}
-
-	@Override /* Object */
-	public String toString() {
-		StringBuilder sb = new StringBuilder("CallRouter: [\n");
-		for (CallMethod sm : callMethods)
-			sb.append("\t" + sm + "\n");
-		sb.append("]");
-		return sb.toString();
-	}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
index 53e929e..adcebbb 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
@@ -44,7 +44,7 @@ public class RestCallHandler {
 
 	private final RestContext context;
 	private final RestLogger logger;
-	private final Map<String,CallRouter> callRouters;
+	private final Map<String,RestCallRouter> restCallRouters;
 
 	/**
 	 * Constructor.
@@ -54,7 +54,7 @@ public class RestCallHandler {
 	public RestCallHandler(RestContext context) {
 		this.context = context;
 		this.logger = context.getLogger();
-		this.callRouters = context.getCallRouters();
+		this.restCallRouters = context.getCallRouters();
 	}
 
 	/**
@@ -153,10 +153,10 @@ public class RestCallHandler {
 			} else {
 				// If the specified method has been defined in a subclass, invoke it.
 				int rc = SC_METHOD_NOT_ALLOWED;
-				if (callRouters.containsKey(methodUC)) {
-					rc = callRouters.get(methodUC).invoke(pathInfo, req, res);
-				} else if (callRouters.containsKey("*")) {
-					rc = callRouters.get("*").invoke(pathInfo, req, res);
+				if (restCallRouters.containsKey(methodUC)) {
+					rc = restCallRouters.get(methodUC).invoke(pathInfo, req, res);
+				} else if (restCallRouters.containsKey("*")) {
+					rc = restCallRouters.get("*").invoke(pathInfo, req, res);
 				}
 
 				// If not invoked above, see if it's an OPTIONs request

http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
new file mode 100644
index 0000000..1cf1fc7
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallRouter.java
@@ -0,0 +1,100 @@
+// ***************************************************************************************************************************
+// * 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 javax.servlet.http.HttpServletResponse.*;
+
+import java.util.*;
+
+import javax.servlet.http.*;
+
+/**
+ * Represents a group of CallMethods on a REST resource that handle the same HTTP Method name but with different
+ * paths/matchers/guards/etc...
+ *
+ * <p>
+ * Incoming requests for a particular HTTP method type (e.g. <js>"GET"</js>) are handed off to this class and then
+ * dispatched to the appropriate RestJavaMethod.
+ */
+class RestCallRouter {
+	private final RestJavaMethod[] restJavaMethods;
+
+	RestCallRouter(RestJavaMethod[] callMethods) {
+		this.restJavaMethods = callMethods;
+	}
+
+	/**
+	 * Builder class.
+	 */
+	static final class Builder {
+		private List<RestJavaMethod> childMethods = new ArrayList<>();
+		private Set<String> collisions = new HashSet<>();
+		private String httpMethodName;
+
+		Builder(String httpMethodName) {
+			this.httpMethodName = httpMethodName;
+		}
+
+		String getHttpMethodName() {
+			return httpMethodName;
+		}
+
+		Builder add(RestJavaMethod m) throws RestServletException {
+			if (! m.hasGuardsOrMatchers()) {
+				String p = m.getHttpMethod() + ":" + m.getPathPattern();
+				if (collisions.contains(p))
+					throw new RestServletException("Duplicate Java methods assigned to the same method/pattern:  ''{0}''", p);
+				collisions.add(p);
+			}
+			childMethods.add(m);
+			return this;
+		}
+
+		RestCallRouter build() {
+			Collections.sort(childMethods);
+			return new RestCallRouter(childMethods.toArray(new RestJavaMethod[childMethods.size()]));
+		}
+	}
+
+	/**
+	 * Workhorse method.
+	 *
+	 * <p>
+	 * Routes this request to one of the CallMethods.
+	 *
+	 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
+	 * @return The HTTP response code.
+	 */
+	int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+		if (restJavaMethods.length == 1)
+			return restJavaMethods[0].invoke(pathInfo, req, res);
+
+		int maxRc = 0;
+		for (RestJavaMethod m : restJavaMethods) {
+			int rc = m.invoke(pathInfo, req, res);
+			if (rc == SC_OK)
+				return SC_OK;
+			maxRc = Math.max(maxRc, rc);
+		}
+		return maxRc;
+	}
+
+	@Override /* Object */
+	public String toString() {
+		StringBuilder sb = new StringBuilder("RestCallRouter: [\n");
+		for (RestJavaMethod sm : restJavaMethods)
+			sb.append("\t" + sm + "\n");
+		sb.append("]");
+		return sb.toString();
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 0810abc..7ab8ee7 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -1434,8 +1434,8 @@ public final class RestContext extends BeanContext {
 	private final MessageBundle msgs;
 	private final ConfigFile configFile;
 	private final VarResolver varResolver;
-	private final Map<String,CallRouter> callRouters;
-	private final Map<String,CallMethod> callMethods;
+	private final Map<String,RestCallRouter> callRouters;
+	private final Map<String,RestJavaMethod> callMethods;
 	private final Map<String,RestContext> childResources;
 	private final RestLogger logger;
 	private final RestCallHandler callHandler;
@@ -1589,8 +1589,8 @@ public final class RestContext extends BeanContext {
 			// Done after initializing fields above since we pass this object to the child resources.
 			//----------------------------------------------------------------------------------------------------
 			List<String> methodsFound = new LinkedList<>();   // Temporary to help debug transient duplicate method issue.
-			Map<String,CallRouter.Builder> routers = new LinkedHashMap<>();
-			Map<String,CallMethod> _javaRestMethods = new LinkedHashMap<>();
+			Map<String,RestCallRouter.Builder> routers = new LinkedHashMap<>();
+			Map<String,RestJavaMethod> _javaRestMethods = new LinkedHashMap<>();
 			Map<String,Method>
 				_startCallMethods = new LinkedHashMap<>(),
 				_preCallMethods = new LinkedHashMap<>(),
@@ -1617,7 +1617,7 @@ public final class RestContext extends BeanContext {
 						if (! Modifier.isPublic(method.getModifiers()))
 							throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", resourceClass.getName(), method.getName());
 
-						CallMethod sm = new CallMethod(resource, method, this);
+						RestJavaMethod sm = new RestJavaMethod(resource, method, this);
 						String httpMethod = sm.getHttpMethod();
 
 						// PROXY is a special case where a method returns an interface that we
@@ -1630,7 +1630,7 @@ public final class RestContext extends BeanContext {
 							if (remoteableMethods.isEmpty())
 								throw new RestException(SC_INTERNAL_SERVER_ERROR, "Method {0} returns an interface {1} that doesn't define any remoteable methods.", getMethodSignature(method), interfaceClass.getReadableName());
 
-							sm = new CallMethod(resource, method, this) {
+							sm = new RestJavaMethod(resource, method, this) {
 
 								@Override
 								int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
@@ -1769,8 +1769,8 @@ public final class RestContext extends BeanContext {
 			this.postInitChildFirstMethodParams = _postInitChildFirstMethodParams.toArray(new Class[_postInitChildFirstMethodParams.size()][]);
 			this.destroyMethodParams = _destroyMethodParams.toArray(new Class[_destroyMethodParams.size()][]);
 
-			Map<String,CallRouter> _callRouters = new LinkedHashMap<>();
-			for (CallRouter.Builder crb : routers.values())
+			Map<String,RestCallRouter> _callRouters = new LinkedHashMap<>();
+			for (RestCallRouter.Builder crb : routers.values())
 				_callRouters.put(crb.getHttpMethodName(), crb.build());
 			this.callRouters = Collections.unmodifiableMap(_callRouters);
 
@@ -1829,9 +1829,9 @@ public final class RestContext extends BeanContext {
 		}
 	}
 
-	private static void addToRouter(Map<String, CallRouter.Builder> routers, String httpMethodName, CallMethod cm) throws RestServletException {
+	private static void addToRouter(Map<String, RestCallRouter.Builder> routers, String httpMethodName, RestJavaMethod cm) throws RestServletException {
 		if (! routers.containsKey(httpMethodName))
-			routers.put(httpMethodName, new CallRouter.Builder(httpMethodName));
+			routers.put(httpMethodName, new RestCallRouter.Builder(httpMethodName));
 		routers.get(httpMethodName).add(cm);
 	}
 
@@ -2214,7 +2214,7 @@ public final class RestContext extends BeanContext {
 	 *
 	 * @return A map with HTTP method names upper-cased as the keys, and call routers as the values.
 	 */
-	protected Map<String,CallRouter> getCallRouters() {
+	protected Map<String,RestCallRouter> getCallRouters() {
 		return callRouters;
 	}
 
@@ -2848,7 +2848,7 @@ public final class RestContext extends BeanContext {
 	 *
 	 * @return A map of Java method names to call method objects.
 	 */
-	protected Map<String,CallMethod> getCallMethods() {
+	protected Map<String,RestJavaMethod> getCallMethods() {
 		return callMethods;
 	}
 

http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
index e40e26a..5bef97b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestInfoProvider.java
@@ -141,7 +141,7 @@ public class RestInfoProvider {
 				.tags(getTags(req))
 				.externalDocs(getExternalDocs(req));
 
-			for (CallMethod sm : context.getCallMethods().values()) {
+			for (RestJavaMethod sm : context.getCallMethods().values()) {
 				if (sm.isRequestAllowed(req)) {
 					Operation o = sm.getSwaggerOperation(req);
 					s.path(
@@ -207,7 +207,7 @@ public class RestInfoProvider {
 	 * @return The localized summary of the method, or a blank string if no summary was found.
 	 */
 	public String getMethodSummary(String javaMethodName, RestRequest req) {
-		CallMethod m = context.getCallMethods().get(javaMethodName);
+		RestJavaMethod m = context.getCallMethods().get(javaMethodName);
 		if (m != null)
 			return m.getSummary(req);
 		return "";
@@ -257,7 +257,7 @@ public class RestInfoProvider {
 	 * @return The localized description of the method, or a blank string if no description was found.
 	 */
 	public String getMethodDescription(String javaMethodName, RestRequest req) {
-		CallMethod m = context.getCallMethods().get(javaMethodName);
+		RestJavaMethod m = context.getCallMethods().get(javaMethodName);
 		if (m != null)
 			return m.getDescription(req);
 		return "";

http://git-wip-us.apache.org/repos/asf/juneau/blob/96fae4f9/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
----------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
new file mode 100644
index 0000000..e172e69
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestJavaMethod.java
@@ -0,0 +1,921 @@
+// ***************************************************************************************************************************
+// * 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 javax.servlet.http.HttpServletResponse.*;
+import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.Utils.*;
+import static org.apache.juneau.BeanContext.*;
+import static org.apache.juneau.rest.RestContext.*;
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.dto.swagger.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.httppart.HttpPartParser;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.widget.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}.
+ */
+class RestJavaMethod implements Comparable<RestJavaMethod>  {
+	private final java.lang.reflect.Method method;
+	private final String httpMethod;
+	private final UrlPathPattern pathPattern;
+	private final RestParam[] params;
+	private final RestGuard[] guards;
+	private final RestMatcher[] optionalMatchers;
+	private final RestMatcher[] requiredMatchers;
+	private final RestConverter[] converters;
+	private final SerializerGroup serializers;
+	private final ParserGroup parsers;
+	private final EncoderGroup encoders;
+	private final HttpPartParser partParser;
+	private final HttpPartSerializer partSerializer;
+	private final ObjectMap properties;
+	private final Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
+	private final String defaultCharset;
+	private final long maxInput;
+	private final boolean deprecated;
+	private final String description, tags, summary, externalDocs;
+	private final Integer priority;
+	private final org.apache.juneau.rest.annotation.Parameter[] parameters;
+	private final Response[] responses;
+	private final RestContext context;
+	private final BeanContext beanContext;
+	private final Map<String,Widget> widgets;
+	private final List<MediaType> supportedAcceptTypes, supportedContentTypes;
+
+	RestJavaMethod(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
+		Builder b = new Builder(servlet, method, context);
+		this.context = context;
+		this.method = method;
+		this.httpMethod = b.httpMethod;
+		this.pathPattern = b.pathPattern;
+		this.params = b.params;
+		this.guards = b.guards;
+		this.optionalMatchers = b.optionalMatchers;
+		this.requiredMatchers = b.requiredMatchers;
+		this.converters = b.converters;
+		this.serializers = b.serializers;
+		this.parsers = b.parsers;
+		this.encoders = b.encoders;
+		this.partParser = b.partParser;
+		this.partSerializer = b.partSerializer;
+		this.beanContext = b.beanContext;
+		this.properties = b.properties;
+		this.defaultRequestHeaders = b.defaultRequestHeaders;
+		this.defaultQuery = b.defaultQuery;
+		this.defaultFormData = b.defaultFormData;
+		this.defaultCharset = b.defaultCharset;
+		this.maxInput = b.maxInput;
+		this.deprecated = b.deprecated;
+		this.description = b.description;
+		this.tags = b.tags;
+		this.summary = b.summary;
+		this.externalDocs = b.externalDocs;
+		this.priority = b.priority;
+		this.parameters = b.parameters;
+		this.responses = b.responses;
+		this.supportedAcceptTypes = b.supportedAcceptTypes;
+		this.supportedContentTypes = b.supportedContentTypes;
+		this.widgets = Collections.unmodifiableMap(b.widgets);
+	}
+
+	private static final class Builder  {
+		String httpMethod, defaultCharset, description, tags, summary, externalDocs;
+		UrlPathPattern pathPattern;
+		RestParam[] params;
+		RestGuard[] guards;
+		RestMatcher[] optionalMatchers, requiredMatchers;
+		RestConverter[] converters;
+		SerializerGroup serializers;
+		ParserGroup parsers;
+		EncoderGroup encoders;
+		HttpPartParser partParser;
+		HttpPartSerializer partSerializer;
+		BeanContext beanContext;
+		ObjectMap properties;
+		Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData;
+		boolean deprecated;
+		long maxInput;
+		Integer priority;
+		org.apache.juneau.rest.annotation.Parameter[] parameters;
+		Response[] responses;
+		Map<String,Widget> widgets;
+		List<MediaType> supportedAcceptTypes, supportedContentTypes;
+
+		Builder(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException {
+			String sig = method.getDeclaringClass().getName() + '.' + method.getName();
+
+			try {
+
+				RestMethod m = method.getAnnotation(RestMethod.class);
+				if (m == null)
+					throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
+
+				if (! m.description().isEmpty())
+					description = m.description();
+				MethodSwagger sm = m.swagger();
+				if (! sm.tags().isEmpty())
+					tags = sm.tags();
+				if (! m.summary().isEmpty())
+					summary = m.summary();
+				if (! sm.externalDocs().isEmpty())
+					externalDocs = sm.externalDocs();
+				deprecated = sm.deprecated();
+				parameters = sm.parameters();
+				responses = sm.responses();
+				serializers = context.getSerializers();
+				parsers = context.getParsers();
+				partSerializer = context.getPartSerializer();
+				partParser = context.getPartParser();
+				beanContext = context.getBeanContext();
+				encoders = context.getEncoders();
+				properties = new ObjectMap().setInner(context.getProperties());
+				defaultCharset = context.getDefaultCharset();
+				maxInput = context.getMaxInput();
+
+				if (! m.defaultCharset().isEmpty())
+					defaultCharset = context.getVarResolver().resolve(m.defaultCharset());
+				if (! m.maxInput().isEmpty())
+					maxInput = StringUtils.parseLongWithSuffix(context.getVarResolver().resolve(m.maxInput()));
+
+				HtmlDocBuilder hdb = new HtmlDocBuilder(properties);
+
+				HtmlDoc hd = m.htmldoc();
+				hdb.process(hd);
+
+				widgets = new HashMap<>(context.getWidgets());
+				for (Class<? extends Widget> wc : hd.widgets()) {
+					Widget w = beanContext.newInstance(Widget.class, wc);
+					widgets.put(w.getName(), w);
+					hdb.script("INHERIT", "$W{"+w.getName()+".script}");
+					hdb.style("INHERIT", "$W{"+w.getName()+".style}");
+				}
+
+				ASet<String> inherit = new ASet<String>().appendAll(StringUtils.split(m.inherit()));
+				if (inherit.contains("*")) 
+					inherit.appendAll("SERIALIZERS","PARSERS","TRANSFORMS","PROPERTIES","ENCODERS");
+
+				SerializerGroupBuilder sgb = null;
+				ParserGroupBuilder pgb = null;
+				ParserBuilder uepb = null;
+				BeanContextBuilder bcb = null;
+				PropertyStore cps = context.getPropertyStore();
+
+				if (m.serializers().length > 0 || m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0
+						|| m.beanFilters().length > 0 || m.pojoSwaps().length > 0 || m.bpi().length > 0
+						|| m.bpx().length > 0) {
+					sgb = SerializerGroup.create();
+					pgb = ParserGroup.create();
+					uepb = Parser.create();
+					bcb = beanContext.builder();
+
+					if (inherit.contains("SERIALIZERS") || m.serializers().length == 0)
+						sgb.append(cps.getArrayProperty(REST_serializers, Object.class));
+
+					if (inherit.contains("PARSERS") || m.parsers().length == 0)
+						pgb.append(cps.getArrayProperty(REST_parsers, Object.class));
+				}
+
+				httpMethod = m.name().toUpperCase(Locale.ENGLISH);
+				if (httpMethod.equals("") && method.getName().startsWith("do"))
+					httpMethod = method.getName().substring(2).toUpperCase(Locale.ENGLISH);
+				if (httpMethod.equals(""))
+					httpMethod = "GET";
+				if (httpMethod.equals("METHOD"))
+					httpMethod = "*";
+
+				priority = m.priority();
+
+				String p = m.path();
+				converters = new RestConverter[m.converters().length];
+				for (int i = 0; i < converters.length; i++)
+					converters[i] = beanContext.newInstance(RestConverter.class, m.converters()[i]);
+
+				guards = new RestGuard[m.guards().length];
+				for (int i = 0; i < guards.length; i++)
+					guards[i] = beanContext.newInstance(RestGuard.class, m.guards()[i]);
+
+				List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>();
+				for (int i = 0; i < m.matchers().length; i++) {
+					Class<? extends RestMatcher> c = m.matchers()[i];
+					RestMatcher matcher = beanContext.newInstance(RestMatcher.class, c, true, servlet, method);
+					if (matcher.mustMatch())
+						requiredMatchers.add(matcher);
+					else
+						optionalMatchers.add(matcher);
+				}
+				if (! m.clientVersion().isEmpty())
+					requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), method));
+
+				this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
+				this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
+
+				PropertyStore ps = context.getPropertyStore();
+				if (! inherit.contains("TRANSFORMS"))
+					ps = ps.builder().set(BEAN_beanFilters, null).set(BEAN_pojoSwaps, null).build();
+				
+				if (sgb != null) {
+					sgb.append(m.serializers());
+				
+					if (! inherit.contains("PROPERTIES"))
+						sgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
+					else
+						sgb.apply(ps);
+					for (Property p1 : m.properties())
+						sgb.set(p1.name(), p1.value());
+					for (String p1 : m.flags())
+						sgb.set(p1, true);
+					if (m.bpi().length > 0) {
+						Map<String,String> bpiMap = new LinkedHashMap<>();
+						for (String s : m.bpi()) {
+							for (String s2 : split(s, ';')) {
+								int i = s2.indexOf(':');
+								if (i == -1)
+									throw new RestServletException(
+										"Invalid format for @RestMethod.bpi() on method ''{0}''.  Must be in the format \"ClassName: comma-delimited-tokens\".  \nValue: {1}", sig, s);
+								bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
+							}
+						}
+						sgb.includeProperties(bpiMap);
+					}
+					if (m.bpx().length > 0) {
+						Map<String,String> bpxMap = new LinkedHashMap<>();
+						for (String s : m.bpx()) {
+							for (String s2 : split(s, ';')) {
+								int i = s2.indexOf(':');
+								if (i == -1)
+									throw new RestServletException(
+										"Invalid format for @RestMethod.bpx() on method ''{0}''.  Must be in the format \"ClassName: comma-delimited-tokens\".  \nValue: {1}", sig, s);
+								bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
+							}
+						}
+						sgb.excludeProperties(bpxMap);
+					}
+					sgb.beanFilters(m.beanFilters());
+					sgb.pojoSwaps(m.pojoSwaps());
+				}
+
+				if (pgb != null) {
+					pgb.append(m.parsers());
+					if (! inherit.contains("PROPERTIES"))
+						pgb.beanFilters(ps.getClassArrayProperty(BEAN_beanFilters)).pojoSwaps(ps.getClassArrayProperty(BEAN_pojoSwaps));
+					else
+						pgb.apply(ps);
+					for (Property p1 : m.properties())
+						pgb.set(p1.name(), p1.value());
+					for (String p1 : m.flags())
+						pgb.set(p1, true);
+					pgb.beanFilters(m.beanFilters());
+					pgb.pojoSwaps(m.pojoSwaps());
+				}
+
+				if (uepb != null) {
+					uepb.apply(ps);
+					for (Property p1 : m.properties())
+						uepb.set(p1.name(), p1.value());
+					for (String p1 : m.flags())
+						uepb.set(p1, true);
+					uepb.beanFilters(m.beanFilters());
+					uepb.pojoSwaps(m.pojoSwaps());
+				}
+				
+				if (bcb != null) {
+					bcb.apply(ps);
+					for (Property p1 : m.properties())
+						bcb.set(p1.name(), p1.value());
+					for (String p1 : m.flags())
+						bcb.set(p1, true);
+					bcb.beanFilters(m.beanFilters());
+					bcb.pojoSwaps(m.pojoSwaps());
+				}
+				
+				if (m.properties().length > 0 || m.flags().length > 0) {
+					properties = new ObjectMap().setInner(properties);
+					for (Property p1 : m.properties())
+						properties.put(p1.name(), p1.value());
+					for (String p1 : m.flags())
+						properties.put(p1, true);
+				}
+
+				if (m.encoders().length > 0) {
+					EncoderGroupBuilder g = EncoderGroup.create().append(IdentityEncoder.INSTANCE);
+					if (inherit.contains("ENCODERS"))
+						g.append(encoders);
+
+					for (Class<? extends Encoder> c : m.encoders()) {
+						try {
+							g.append(c);
+						} catch (Exception e) {
+							throw new RestServletException(
+								"Exception occurred while trying to instantiate Encoder on method ''{0}'': ''{1}''", sig, c.getSimpleName()).initCause(e);
+						}
+					}
+					encoders = g.build();
+				}
+
+				defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+				for (String s : m.defaultRequestHeaders()) {
+					String[] h = RestUtils.parseKeyValuePair(s);
+					if (h == null)
+						throw new RestServletException(
+							"Invalid default request header specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
+					defaultRequestHeaders.put(h[0], h[1]);
+				}
+
+				defaultQuery = new LinkedHashMap<>();
+				for (String s : m.defaultQuery()) {
+					String[] h = RestUtils.parseKeyValuePair(s);
+					if (h == null)
+						throw new RestServletException(
+							"Invalid default query parameter specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
+					defaultQuery.put(h[0], h[1]);
+				}
+
+				defaultFormData = new LinkedHashMap<>();
+				for (String s : m.defaultFormData()) {
+					String[] h = RestUtils.parseKeyValuePair(s);
+					if (h == null)
+						throw new RestServletException(
+							"Invalid default form data parameter specified on method ''{0}'': ''{1}''.  Must be in the format: ''name[:=]value''", sig, s);
+					defaultFormData.put(h[0], h[1]);
+				}
+
+				Type[] pt = method.getGenericParameterTypes();
+				Annotation[][] pa = method.getParameterAnnotations();
+				for (int i = 0; i < pt.length; i++) {
+					for (Annotation a : pa[i]) {
+						if (a instanceof Header) {
+							Header h = (Header)a;
+							if (! h.def().isEmpty())
+								defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), h.def());
+						} else if (a instanceof Query) {
+							Query q = (Query)a;
+							if (! q.def().isEmpty())
+								defaultQuery.put(firstNonEmpty(q.name(), q.value()), q.def());
+						} else if (a instanceof FormData) {
+							FormData f = (FormData)a;
+							if (! f.def().isEmpty())
+								defaultFormData.put(firstNonEmpty(f.name(), f.value()), f.def());
+						}
+					}
+				}
+
+				pathPattern = new UrlPathPattern(p);
+
+				if (sgb != null) 
+					serializers = sgb.build();
+				if (pgb != null)
+					parsers = pgb.build();
+				if (uepb != null && partParser instanceof Parser) {
+					Parser pp = (Parser)partParser;
+					partParser = (HttpPartParser)pp.builder().apply(uepb.getPropertyStore()).build();
+				}
+				if (bcb != null)
+					beanContext = bcb.build();
+
+				supportedAcceptTypes = 
+					m.supportedAcceptTypes().length > 0 
+					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedAcceptTypes())))) 
+					: serializers.getSupportedMediaTypes();
+				supportedContentTypes =
+					m.supportedContentTypes().length > 0 
+					? Collections.unmodifiableList(new ArrayList<>(Arrays.asList(MediaType.forStrings(m.supportedContentTypes())))) 
+					: parsers.getSupportedMediaTypes();
+					
+				params = context.findParams(method, pathPattern, false);
+
+				// Need this to access methods in anonymous inner classes.
+				method.setAccessible(true);
+			} catch (RestServletException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new RestServletException("Exception occurred while initializing method ''{0}''", sig).initCause(e);
+			}
+		}
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this Java method has any guards or matchers.
+	 */
+	boolean hasGuardsOrMatchers() {
+		return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0);
+	}
+
+	/**
+	 * Returns the HTTP method name (e.g. <js>"GET"</js>).
+	 */
+	String getHttpMethod() {
+		return httpMethod;
+	}
+
+	/**
+	 * Returns the path pattern for this method.
+	 */
+	String getPathPattern() {
+		return pathPattern.toString();
+	}
+
+	/**
+	 * Returns the localized Swagger for this Java method.
+	 */
+	Operation getSwaggerOperation(RestRequest req) throws ParseException {
+		Operation o = operation()
+			.operationId(method.getName())
+			.description(getDescription(req))
+			.tags(getTags(req))
+			.summary(getSummary(req))
+			.externalDocs(getExternalDocs(req))
+			.parameters(getParameters(req))
+			.responses(getResponses(req));
+
+		if (isDeprecated())
+			o.deprecated(true);
+
+		if (! parsers.getSupportedMediaTypes().equals(context.getParsers().getSupportedMediaTypes()))
+			o.consumes(parsers.getSupportedMediaTypes());
+
+		if (! serializers.getSupportedMediaTypes().equals(context.getSerializers().getSupportedMediaTypes()))
+			o.produces(serializers.getSupportedMediaTypes());
+
+		return o;
+	}
+
+	private Operation getSwaggerOperationFromFile(RestRequest req) {
+		Swagger s = req.getSwaggerFromFile();
+		if (s != null && s.getPaths() != null && s.getPaths().get(pathPattern.getPatternString()) != null)
+			return s.getPaths().get(pathPattern.getPatternString()).get(httpMethod);
+		return null;
+	}
+
+	/**
+	 * Returns the localized summary for this Java method.
+	 */
+	String getSummary(RestRequest req) {
+		VarResolverSession vr = req.getVarResolverSession();
+		if (summary != null)
+			return vr.resolve(summary);
+		String summary = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".summary");
+		if (summary != null)
+			return vr.resolve(summary);
+		Operation o = getSwaggerOperationFromFile(req);
+		if (o != null)
+			return o.getSummary();
+		return null;
+	}
+
+	/**
+	 * Returns the localized description for this Java method.
+	 */
+	String getDescription(RestRequest req) {
+		VarResolverSession vr = req.getVarResolverSession();
+		if (description != null)
+			return vr.resolve(description);
+		String description = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".description");
+		if (description != null)
+			return vr.resolve(description);
+		Operation o = getSwaggerOperationFromFile(req);
+		if (o != null)
+			return o.getDescription();
+		return null;
+	}
+
+	/**
+	 * Returns the localized Swagger tags for this Java method.
+	 */
+	private List<String> getTags(RestRequest req) {
+		VarResolverSession vr = req.getVarResolverSession();
+		JsonParser jp = JsonParser.DEFAULT;
+		try {
+			if (tags != null)
+				return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
+			String tags = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".tags");
+			if (tags != null)
+				return jp.parse(vr.resolve(tags), ArrayList.class, String.class);
+			Operation o = getSwaggerOperationFromFile(req);
+			if (o != null)
+				return o.getTags();
+			return null;
+		} catch (Exception e) {
+			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+		}
+	}
+
+	/**
+	 * Returns the localized Swagger external docs for this Java method.
+	 */
+	private ExternalDocumentation getExternalDocs(RestRequest req) {
+		VarResolverSession vr = req.getVarResolverSession();
+		JsonParser jp = JsonParser.DEFAULT;
+		try {
+			if (externalDocs != null)
+				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
+			String externalDocs = context.getMessages().findFirstString(req.getLocale(), method.getName() + ".externalDocs");
+			if (externalDocs != null)
+				return jp.parse(vr.resolve(externalDocs), ExternalDocumentation.class);
+			Operation o = getSwaggerOperationFromFile(req);
+			if (o != null)
+				return o.getExternalDocs();
+			return null;
+		} catch (Exception e) {
+			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+		}
+	}
+
+	/**
+	 * Returns the Swagger deprecated flag for this Java method.
+	 */
+	private boolean isDeprecated() {
+		return deprecated;
+	}
+
+	/**
+	 * Returns the localized Swagger parameter information for this Java method.
+	 */
+	private List<ParameterInfo> getParameters(RestRequest req) throws ParseException {
+		Operation o = getSwaggerOperationFromFile(req);
+		if (o != null && o.getParameters() != null)
+			return o.getParameters();
+
+		VarResolverSession vr = req.getVarResolverSession();
+		JsonParser jp = JsonParser.DEFAULT;
+		Map<String,ParameterInfo> m = new TreeMap<>();
+
+		// First parse @RestMethod.parameters() annotation.
+		for (org.apache.juneau.rest.annotation.Parameter v : parameters) {
+			String in = vr.resolve(v.in());
+			ParameterInfo p = parameterInfo(in, vr.resolve(v.name()));
+
+			if (! v.description().isEmpty())
+				p.description(vr.resolve(v.description()));
+			if (v.required())
+				p.required(v.required());
+
+			if ("body".equals(in)) {
+				if (! v.schema().isEmpty())
+					p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
+			} else {
+				if (v.allowEmptyValue())
+					p.allowEmptyValue(v.allowEmptyValue());
+				if (! v.collectionFormat().isEmpty())
+					p.collectionFormat(vr.resolve(v.collectionFormat()));
+				if (! v._default().isEmpty())
+					p._default(vr.resolve(v._default()));
+				if (! v.format().isEmpty())
+					p.format(vr.resolve(v.format()));
+				if (! v.items().isEmpty())
+					p.items(jp.parse(vr.resolve(v.items()), Items.class));
+				p.type(vr.resolve(v.type()));
+			}
+			m.put(p.getIn() + '.' + p.getName(), p);
+		}
+
+		// Next, look in resource bundle.
+		String prefix = method.getName() + ".req";
+		for (String key : context.getMessages().keySet(prefix)) {
+			if (key.length() > prefix.length()) {
+				String value = vr.resolve(context.getMessages().getString(key));
+				String[] parts = key.substring(prefix.length() + 1).split("\\.");
+				String in = parts[0], name, field;
+				boolean isBody = "body".equals(in);
+				if (parts.length == (isBody ? 2 : 3)) {
+					if ("body".equals(in)) {
+						name = null;
+						field = parts[1];
+					} else {
+						name = parts[1];
+						field = parts[2];
+					}
+					String k2 = in + '.' + name;
+					ParameterInfo p = m.get(k2);
+					if (p == null) {
+						p = parameterInfoStrict(in, name);
+						m.put(k2, p);
+					}
+
+					if (field.equals("description"))
+						p.description(value);
+					else if (field.equals("required"))
+						p.required(Boolean.valueOf(value));
+
+					if ("body".equals(in)) {
+						if (field.equals("schema"))
+							p.schema(jp.parse(value, SchemaInfo.class));
+					} else {
+						if (field.equals("allowEmptyValue"))
+							p.allowEmptyValue(Boolean.valueOf(value));
+						else if (field.equals("collectionFormat"))
+							p.collectionFormat(value);
+						else if (field.equals("default"))
+							p._default(value);
+						else if (field.equals("format"))
+							p.format(value);
+						else if (field.equals("items"))
+							p.items(jp.parse(value, Items.class));
+						else if (field.equals("type"))
+							p.type(value);
+					}
+				} else {
+					System.err.println("Unknown bundle key '"+key+"'");
+				}
+			}
+		}
+
+		// Finally, look for parameters defined on method.
+		for (RestParam mp : this.params) {
+			RestParamType in = mp.getParamType();
+			if (in != RestParamType.OTHER) {
+				String k2 = in.toString() + '.' + (in == RestParamType.BODY ? null : mp.getName());
+				ParameterInfo p = m.get(k2);
+				if (p == null) {
+					p = parameterInfoStrict(in.toString(), mp.getName());
+					m.put(k2, p);
+				}
+			}
+		}
+
+		if (m.isEmpty())
+			return null;
+		return new ArrayList<>(m.values());
+	}
+
+	/**
+	 * Returns the localized Swagger response information about this Java method.
+	 */
+	private Map<Integer,ResponseInfo> getResponses(RestRequest req) throws ParseException {
+		Operation o = getSwaggerOperationFromFile(req);
+		if (o != null && o.getResponses() != null)
+			return o.getResponses();
+
+		VarResolverSession vr = req.getVarResolverSession();
+		JsonParser jp = JsonParser.DEFAULT;
+		Map<Integer,ResponseInfo> m = new TreeMap<>();
+		Map<String,HeaderInfo> m2 = new TreeMap<>();
+
+		// First parse @RestMethod.parameters() annotation.
+		for (Response r : responses) {
+			int httpCode = r.value();
+			String description = r.description().isEmpty() ? RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
+			ResponseInfo r2 = responseInfo(description);
+
+			if (r.headers().length > 0) {
+				for (org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
+					HeaderInfo h = headerInfoStrict(vr.resolve(v.type()));
+					if (! v.collectionFormat().isEmpty())
+						h.collectionFormat(vr.resolve(v.collectionFormat()));
+					if (! v._default().isEmpty())
+						h._default(vr.resolve(v._default()));
+					if (! v.description().isEmpty())
+						h.description(vr.resolve(v.description()));
+					if (! v.format().isEmpty())
+						h.format(vr.resolve(v.format()));
+					if (! v.items().isEmpty())
+						h.items(jp.parse(vr.resolve(v.items()), Items.class));
+					r2.header(v.name(), h);
+					m2.put(httpCode + '.' + v.name(), h);
+				}
+			}
+			m.put(httpCode, r2);
+		}
+
+		// Next, look in resource bundle.
+		String prefix = method.getName() + ".res";
+		for (String key : context.getMessages().keySet(prefix)) {
+			if (key.length() > prefix.length()) {
+				String value = vr.resolve(context.getMessages().getString(key));
+				String[] parts = key.substring(prefix.length() + 1).split("\\.");
+				int httpCode = Integer.parseInt(parts[0]);
+				ResponseInfo r2 = m.get(httpCode);
+				if (r2 == null) {
+					r2 = responseInfo(null);
+					m.put(httpCode, r2);
+				}
+
+				String name = parts.length > 1 ? parts[1] : "";
+
+				if ("header".equals(name) && parts.length > 3) {
+					String headerName = parts[2];
+					String field = parts[3];
+
+					String k2 = httpCode + '.' + headerName;
+					HeaderInfo h = m2.get(k2);
+					if (h == null) {
+						h = headerInfoStrict("string");
+						m2.put(k2, h);
+						r2.header(name, h);
+					}
+					if (field.equals("collectionFormat"))
+						h.collectionFormat(value);
+					else if (field.equals("default"))
+						h._default(value);
+					else if (field.equals("description"))
+						h.description(value);
+					else if (field.equals("format"))
+						h.format(value);
+					else if (field.equals("items"))
+						h.items(jp.parse(value, Items.class));
+					else if (field.equals("type"))
+						h.type(value);
+
+				} else if ("description".equals(name)) {
+					r2.description(value);
+				} else if ("schema".equals(name)) {
+					r2.schema(jp.parse(value, SchemaInfo.class));
+				} else if ("examples".equals(name)) {
+					r2.examples(jp.parse(value, TreeMap.class));
+				} else {
+					System.err.println("Unknown bundle key '"+key+"'");
+				}
+			}
+		}
+
+		return m.isEmpty() ? null : m;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified request object can call this method.
+	 */
+	boolean isRequestAllowed(RestRequest req) {
+		for (RestGuard guard : guards) {
+			req.setJavaMethod(method);
+			if (! guard.isRequestAllowed(req))
+				return false;
+		}
+		return true;
+	}
+
+	/**
+	 * Workhorse method.
+	 *
+	 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
+	 * @return The HTTP response code.
+	 */
+	int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+
+		String[] patternVals = pathPattern.match(pathInfo);
+		if (patternVals == null)
+			return SC_NOT_FOUND;
+
+		String remainder = null;
+		if (patternVals.length > pathPattern.getVars().length)
+			remainder = patternVals[pathPattern.getVars().length];
+		for (int i = 0; i < pathPattern.getVars().length; i++)
+			req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]);
+		req.getPathMatch().setRemainder(remainder);
+
+		ObjectMap requestProperties = new ResolvingObjectMap(req.getVarResolverSession()).setInner(properties);
+
+		req.init(method, requestProperties, defaultRequestHeaders, defaultQuery, defaultFormData, defaultCharset,
+			maxInput, serializers, parsers, partParser, beanContext, encoders, widgets, supportedAcceptTypes, supportedContentTypes);
+		res.init(requestProperties, defaultCharset, serializers, partSerializer, encoders);
+
+		// Class-level guards
+		for (RestGuard guard : context.getGuards())
+			if (! guard.guard(req, res))
+				return SC_UNAUTHORIZED;
+
+		// If the method implements matchers, test them.
+		for (RestMatcher m : requiredMatchers)
+			if (! m.matches(req))
+				return SC_PRECONDITION_FAILED;
+		if (optionalMatchers.length > 0) {
+			boolean matches = false;
+			for (RestMatcher m : optionalMatchers)
+				matches |= m.matches(req);
+			if (! matches)
+				return SC_PRECONDITION_FAILED;
+		}
+
+		context.preCall(req, res);
+
+		Object[] args = new Object[params.length];
+		for (int i = 0; i < params.length; i++) {
+			try {
+				args[i] = params[i].resolve(req, res);
+			} catch (RestException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new RestException(SC_BAD_REQUEST,
+					"Invalid data conversion.  Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
+					params[i].getParamType().name(), params[i].getName(), params[i].getType(), method.getDeclaringClass().getName(), method.getName()
+				).initCause(e);
+			}
+		}
+
+		try {
+
+			for (RestGuard guard : guards)
+				if (! guard.guard(req, res))
+					return SC_OK;
+
+			Object output = method.invoke(context.getResource(), args);
+			if (! method.getReturnType().equals(Void.TYPE))
+				if (output != null || ! res.getOutputStreamCalled())
+					res.setOutput(output);
+
+			context.postCall(req, res);
+
+			if (res.hasOutput()) {
+				output = res.getOutput();
+				for (RestConverter converter : converters)
+					output = converter.convert(req, output, beanContext.getClassMetaForObject(output));
+				res.setOutput(output);
+			}
+		} catch (IllegalArgumentException e) {
+			throw new RestException(SC_BAD_REQUEST,
+				"Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}",
+				method.toString(), getReadableClassNames(args)
+			).initCause(e);
+		} catch (InvocationTargetException e) {
+			Throwable e2 = e.getTargetException();		// Get the throwable thrown from the doX() method.
+			if (e2 instanceof RestException)
+				throw (RestException)e2;
+			if (e2 instanceof ParseException)
+				throw new RestException(SC_BAD_REQUEST, e2);
+			if (e2 instanceof InvalidDataConversionException)
+				throw new RestException(SC_BAD_REQUEST, e2);
+			throw new RestException(SC_INTERNAL_SERVER_ERROR, e2);
+		} catch (RestException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+		}
+		return SC_OK;
+	}
+
+	@Override /* Object */
+	public String toString() {
+		return "SimpleMethod: name=" + httpMethod + ", path=" + pathPattern.getPatternString();
+	}
+
+	/*
+	 * compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list.
+	 * It maintains the order in which matches are made during requests.
+	 */
+	@Override /* Comparable */
+	public int compareTo(RestJavaMethod o) {
+		int c;
+
+		c = priority.compareTo(o.priority);
+		if (c != 0)
+			return c;
+
+		c = pathPattern.compareTo(o.pathPattern);
+		if (c != 0)
+			return c;
+
+		c = compare(o.requiredMatchers.length, requiredMatchers.length);
+		if (c != 0)
+			return c;
+
+		c = compare(o.optionalMatchers.length, optionalMatchers.length);
+		if (c != 0)
+			return c;
+
+		c = compare(o.guards.length, guards.length);
+		if (c != 0)
+			return c;
+
+		return 0;
+	}
+
+	@Override /* Object */
+	public boolean equals(Object o) {
+		if (! (o instanceof RestJavaMethod))
+			return false;
+		return (compareTo((RestJavaMethod)o) == 0);
+	}
+
+	@Override /* Object */
+	public int hashCode() {
+		return super.hashCode();
+	}
+}
\ No newline at end of file