You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/09/08 23:25:37 UTC

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

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,2053 @@
+// ***************************************************************************************************************************
+// * 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.internal.ArrayUtils.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.IOUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+import javax.activation.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.html.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.ini.*;
+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.annotation.Properties;
+import org.apache.juneau.rest.vars.*;
+import org.apache.juneau.rest.widget.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.svl.vars.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.utils.*;
+
+/**
+ * Contains all the configuration on a REST resource and the entry points for handling REST calls.
+ *
+ * <p>
+ * See {@link PropertyStore} for more information about context properties.
+ */
+public final class RestContext extends Context {
+
+	/**
+	 * <b>Configuration property:</b>  Enable header URL parameters.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.allowHeaderParams"</js>
+	 * 	<li><b>Data type:</b> <code>Boolean</code>
+	 * 	<li><b>Default:</b> <jk>true</jk>
+	 * </ul>
+	 *
+	 * <p>
+	 * When enabled, headers such as <js>"Accept"</js> and <js>"Content-Type"</js> to be passed in as URL query
+	 * parameters.
+	 * For example:  <js>"?Accept=text/json&amp;Content-Type=text/json"</js>
+	 *
+	 * <p>
+	 * Parameter names are case-insensitive.
+	 *
+	 * <p>
+	 * Useful for debugging REST interface using only a browser.
+	 *
+	 * <p>
+	 * Applicable to servlet class only.
+	 */
+	public static final String REST_allowHeaderParams = "RestServlet.allowHeaderParams";
+
+	/**
+	 * <b>Configuration property:</b>  Enable <js>"method"</js> URL parameter for specific HTTP methods.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.allowMethodParam"</js>
+	 * 	<li><b>Data type:</b> <code>String</code>
+	 * 	<li><b>Default:</b> <js>""</js>
+	 * </ul>
+	 *
+	 * <p>
+	 * When specified, the HTTP method can be overridden by passing in a <js>"method"</js> URL parameter on a regular
+	 * GET request.
+	 * For example:  <js>"?method=OPTIONS"</js>
+	 *
+	 * <p>
+	 * Format is a comma-delimited list of HTTP method names that can be passed in as a method parameter.
+	 * Parameter name is case-insensitive.
+	 * Use "*" to represent all methods.
+	 * For backwards compatibility, "true" also means "*".
+	 *
+	 * <p>
+	 * Note that per the <a class="doclink"
+	 * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">HTTP specification</a>, special care should
+	 * be taken when allowing non-safe (POST, PUT, DELETE) methods to be invoked through GET requests.
+	 *
+	 * <p>
+	 * Applicable to servlet class only.
+	 *
+	 * <p>
+	 * Example: <js>"HEAD,OPTIONS"</js>
+	 */
+	public static final String REST_allowMethodParam = "RestServlet.allowMethodParam";
+
+	/**
+	 * <b>Configuration property:</b>  Enable <js>"body"</js> URL parameter.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.allowBodyParam"</js>
+	 * 	<li><b>Data type:</b> <code>Boolean</code>
+	 * 	<li><b>Default:</b> <jk>true</jk>
+	 * </ul>
+	 *
+	 * <p>
+	 * When enabled, the HTTP body content on PUT and POST requests can be passed in as text using the <js>"body"</js>
+	 * URL parameter.
+	 * For example:  <js>"?body={name:'John%20Smith',age:45}"</js>
+	 *
+	 * <p>
+	 * Parameter name is case-insensitive.
+	 *
+	 * <p>
+	 * Useful for debugging PUT and POST methods using only a browser.
+	 *
+	 * <p>
+	 * Applicable to servlet class only.
+	 */
+	public static final String REST_allowBodyParam = "RestServlet.allowBodyParam";
+
+	/**
+	 * <b>Configuration property:</b>  Render stack traces.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.renderResponseStackTraces"</js>
+	 * 	<li><b>Data type:</b> <code>Boolean</code>
+	 * 	<li><b>Default:</b> <jk>false</jk>
+	 * </ul>
+	 *
+	 * <p>
+	 * Render stack traces in HTTP response bodies when errors occur.
+	 *
+	 * <p>
+	 * When enabled, Java stack traces will be rendered in the output response.
+	 * Useful for debugging, although allowing stack traces to be rendered may cause security concerns.
+	 *
+	 * <p>
+	 * Applicable to servlet class only.
+	 */
+	public static final String REST_renderResponseStackTraces = "RestServlet.renderResponseStackTraces";
+
+	/**
+	 * <b>Configuration property:</b>  Use stack trace hashes.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.useStackTraceHashes"</js>
+	 * 	<li><b>Data type:</b> <code>Boolean</code>
+	 * 	<li><b>Default:</b> <jk>true</jk>
+	 * </ul>
+	 *
+	 * <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.
+	 *
+	 * <p>
+	 * Applicable to servlet class only.
+	 */
+	public static final String REST_useStackTraceHashes = "RestServlet.useStackTraceHashes";
+
+	/**
+	 * <b>Configuration property:</b>  Default character encoding.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.defaultCharset"</js>
+	 * 	<li><b>Data type:</b> <code>String</code>
+	 * 	<li><b>Default:</b> <js>"utf-8"</js>
+	 * </ul>
+	 *
+	 * <p>
+	 * The default character encoding for the request and response if not specified on the request.
+	 *
+	 * <p>
+	 * Applicable to servlet class and methods.
+	 */
+	public static final String REST_defaultCharset = "RestServlet.defaultCharset";
+
+	/**
+	 * <b>Configuration property:</b>  Expected format of request parameters.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.paramFormat"</js>
+	 * 	<li><b>Data type:</b> <code>String</code>
+	 * 	<li><b>Default:</b> <js>"UON"</js>
+	 * </ul>
+	 * <p>
+	 * Possible values:
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		<js>"UON"</js> - URL-Encoded Object Notation.
+	 * 		<br>This notation allows for request parameters to contain arbitrarily complex POJOs.
+	 * 	<li>
+	 * 		<js>"PLAIN"</js> - Plain text.
+	 * 		<br>This treats request parameters as plain text.
+	 * 		<br>Only POJOs directly convertible from <l>Strings</l> can be represented in parameters when using this
+	 * 		mode.
+	 * </ul>
+	 *
+	 * <p>
+	 * Note that the parameter value <js>"(foo)"</js> is interpreted as <js>"(foo)"</js> when using plain mode, but
+	 * <js>"foo"</js> when using UON mode.
+	 *
+	 * <p>
+	 * The format can also be specified per-parameter using the {@link FormData#format() @FormData.format()} and
+	 * {@link Query#format() @Query.format()} annotations.
+	 *
+	 * <p>
+	 * Applicable to servlet class and methods.
+	 */
+	public static final String REST_paramFormat = "RestServlet.paramFormat";
+
+	/**
+	 * <b>Configuration property:</b>  REST resource resolver.
+	 *
+	 * <ul>
+	 * 	<li><b>Name:</b> <js>"RestServlet.resourceResolver"</js>
+	 * 	<li><b>Data type:</b> <code>Class</code> or {@link RestResourceResolver}
+	 * 	<li><b>Default:</b> <jk>null</jk>
+	 * </ul>
+	 *
+	 * <p>
+	 * The resource resolver used to instantiate REST resource classes.
+	 *
+	 * <p>
+	 * Applicable to servlet class.
+	 * <br>Can be passed in through servlet context.
+	 */
+	public static final String REST_resourceResolver = "RestServlet.resourceResolver";
+
+
+	private final Object resource;
+	final RestConfig config;
+	private final boolean
+		allowHeaderParams,
+		allowBodyParam,
+		renderResponseStackTraces,
+		useStackTraceHashes;
+	private final String
+		defaultCharset,
+		paramFormat,
+		clientVersionHeader,
+		fullPath,
+		contextPath,
+		htmlHeader,
+		htmlNav,
+		htmlAside,
+		htmlStyle,
+		htmlStylesheet,
+		htmlScript,
+		htmlFooter,
+		htmlNoResultsMessage;
+	private final String[]
+		htmlLinks;
+	private final boolean htmlNoWrap;
+	private final HtmlDocTemplate htmlTemplate;
+	private final Map<String,Widget> htmlWidgets;
+
+	private final Set<String> allowMethodParams;
+
+	private final ObjectMap properties;
+	private final Class<?>[]
+		beanFilters,
+		pojoSwaps;
+	private final Map<Class<?>,RestParam> paramResolvers;
+	private final SerializerGroup serializers;
+	private final ParserGroup parsers;
+	private final UrlEncodingSerializer urlEncodingSerializer;
+	private final UrlEncodingParser urlEncodingParser;
+	private final EncoderGroup encoders;
+	private final MediaType[]
+		supportedContentTypes,
+		supportedAcceptTypes;
+	private final Map<String,String> defaultRequestHeaders;
+	private final Map<String,Object> defaultResponseHeaders;
+	private final BeanContext beanContext;
+	private final RestConverter[] converters;
+	private final RestGuard[] guards;
+	private final ResponseHandler[] responseHandlers;
+	private final MimetypesFileTypeMap mimetypesFileTypeMap;
+	private final StreamResource favIcon;
+	private final Map<String,String> staticFilesMap;
+	private final String[] staticFilesPrefixes;
+	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,RestContext> childResources;
+	private final RestLogger logger;
+	private final RestCallHandler callHandler;
+	private final RestInfoProvider infoProvider;
+	private final RestException initException;
+	private final RestContext parentContext;
+	private final RestResourceResolver resourceResolver;
+
+	// Lifecycle methods
+	private final Method[]
+		postInitMethods,
+		postInitChildFirstMethods,
+		preCallMethods,
+		postCallMethods,
+		startCallMethods,
+		endCallMethods,
+		destroyMethods;
+	private final RestParam[][]
+		preCallMethodParams,
+		postCallMethodParams;
+	private final Class<?>[][]
+		postInitMethodParams,
+		postInitChildFirstMethodParams,
+		startCallMethodParams,
+		endCallMethodParams,
+		destroyMethodParams;
+
+	// In-memory cache of images and stylesheets in the org.apache.juneau.rest.htdocs package.
+	private final Map<String,StreamResource> staticFilesCache = new ConcurrentHashMap<String,StreamResource>();
+
+	private final ResourceFinder resourceFinder;
+	private final ConcurrentHashMap<Integer,AtomicInteger> stackTraceHashes = new ConcurrentHashMap<Integer,AtomicInteger>();
+
+
+	/**
+	 * Constructor.
+	 *
+	 * @param resource The resource class (a class annotated with {@link RestResource @RestResource}).
+	 * @param servletContext
+	 * 	The servlet context object.
+	 * 	Can be <jk>null</jk> if this isn't a
+	 * @param config The servlet configuration object.
+	 * @throws Exception If any initialization problems were encountered.
+	 */
+	@SuppressWarnings("unchecked")
+	public RestContext(Object resource, ServletContext servletContext, RestConfig config) throws Exception {
+		super(null);
+		RestException _initException = null;
+		try {
+			this.resource = resource;
+			this.config = config;
+			this.resourceFinder = new ResourceFinder(resource.getClass());
+			this.parentContext = config.parentContext;
+
+			Builder b = new Builder(resource, servletContext, config);
+			this.allowHeaderParams = b.allowHeaderParams;
+			this.allowBodyParam = b.allowBodyParam;
+			this.renderResponseStackTraces = b.renderResponseStackTraces;
+			this.useStackTraceHashes = b.useStackTraceHashes;
+			this.allowMethodParams = Collections.unmodifiableSet(b.allowMethodParams);
+			this.defaultCharset = b.defaultCharset;
+			this.paramFormat = b.paramFormat;
+			this.varResolver = b.varResolver;
+			this.configFile = b.configFile;
+			this.properties = b.properties;
+			this.beanFilters = b.beanFilters;
+			this.pojoSwaps = b.pojoSwaps;
+			this.paramResolvers = Collections.unmodifiableMap(b.paramResolvers);
+			this.serializers = b.serializers;
+			this.parsers = b.parsers;
+			this.urlEncodingSerializer = b.urlEncodingSerializer;
+			this.urlEncodingParser = b.urlEncodingParser;
+			this.encoders = b.encoders;
+			this.supportedContentTypes = toObjectArray(b.supportedContentTypes, MediaType.class);
+			this.supportedAcceptTypes = toObjectArray(b.supportedAcceptTypes, MediaType.class);
+			this.clientVersionHeader = b.clientVersionHeader;
+			this.defaultRequestHeaders = Collections.unmodifiableMap(b.defaultRequestHeaders);
+			this.defaultResponseHeaders = Collections.unmodifiableMap(b.defaultResponseHeaders);
+			this.beanContext = b.beanContext;
+			this.converters = b.converters.toArray(new RestConverter[b.converters.size()]);
+			this.guards = b.guards.toArray(new RestGuard[b.guards.size()]);
+			this.responseHandlers = toObjectArray(b.responseHandlers, ResponseHandler.class);
+			this.mimetypesFileTypeMap = b.mimetypesFileTypeMap;
+			this.favIcon = b.favIcon;
+			this.staticFilesMap = Collections.unmodifiableMap(b.staticFilesMap);
+			this.staticFilesPrefixes = b.staticFilesPrefixes;
+			this.msgs = b.messageBundle;
+			this.childResources = Collections.synchronizedMap(new LinkedHashMap<String,RestContext>());  // Not unmodifiable on purpose so that children can be replaced.
+			this.logger = b.logger;
+			this.fullPath = b.fullPath;
+			this.contextPath = nullIfEmpty(b.contextPath);
+
+			this.htmlWidgets = Collections.unmodifiableMap(b.htmlWidgets);
+			this.htmlHeader = b.htmlHeader;
+			this.htmlLinks = b.htmlLinks;
+			this.htmlNav = b.htmlNav;
+			this.htmlAside = b.htmlAside;
+			this.htmlStyle = b.htmlStyle;
+			this.htmlStylesheet = b.htmlStylesheet;
+			this.htmlScript = b.htmlScript;
+			this.htmlFooter = b.htmlFooter;
+			this.htmlNoWrap = b.htmlNoWrap;
+			this.htmlNoResultsMessage = b.htmlNoResultsMessage;
+			this.htmlTemplate = b.htmlTemplate;
+
+			//----------------------------------------------------------------------------------------------------
+			// Initialize the child resources.
+			// Done after initializing fields above since we pass this object to the child resources.
+			//----------------------------------------------------------------------------------------------------
+			List<String> methodsFound = new LinkedList<String>();   // Temporary to help debug transient duplicate method issue.
+			Map<String,CallRouter.Builder> routers = new LinkedHashMap<String,CallRouter.Builder>();
+			Map<String,CallMethod> _javaRestMethods = new LinkedHashMap<String,CallMethod>();
+			Map<String,Method>
+				_startCallMethods = new LinkedHashMap<String,Method>(),
+				_preCallMethods = new LinkedHashMap<String,Method>(),
+				_postCallMethods = new LinkedHashMap<String,Method>(),
+				_endCallMethods = new LinkedHashMap<String,Method>(),
+				_postInitMethods = new LinkedHashMap<String,Method>(),
+				_postInitChildFirstMethods = new LinkedHashMap<String,Method>(),
+				_destroyMethods = new LinkedHashMap<String,Method>();
+			List<RestParam[]>
+				_preCallMethodParams = new ArrayList<RestParam[]>(),
+				_postCallMethodParams = new ArrayList<RestParam[]>();
+			List<Class<?>[]>
+				_startCallMethodParams = new ArrayList<Class<?>[]>(),
+				_endCallMethodParams = new ArrayList<Class<?>[]>(),
+				_postInitMethodParams = new ArrayList<Class<?>[]>(),
+				_postInitChildFirstMethodParams = new ArrayList<Class<?>[]>(),
+				_destroyMethodParams = new ArrayList<Class<?>[]>();
+
+			for (java.lang.reflect.Method method : resource.getClass().getMethods()) {
+				if (method.isAnnotationPresent(RestMethod.class)) {
+					RestMethod a = method.getAnnotation(RestMethod.class);
+					methodsFound.add(method.getName() + "," + a.name() + "," + a.path());
+					try {
+						if (! Modifier.isPublic(method.getModifiers()))
+							throw new RestServletException("@RestMethod method {0}.{1} must be defined as public.", this.getClass().getName(), method.getName());
+
+						CallMethod sm = new CallMethod(resource, method, this);
+						String httpMethod = sm.getHttpMethod();
+
+						// PROXY is a special case where a method returns an interface that we
+						// can perform REST calls against.
+						// We override the CallMethod.invoke() method to insert our logic.
+						if ("PROXY".equals(httpMethod)) {
+
+							final ClassMeta<?> interfaceClass = beanContext.getClassMeta(method.getGenericReturnType());
+							final Map<String,Method> remoteableMethods = interfaceClass.getRemoteableMethods();
+							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) {
+
+								@Override
+								int invoke(String pathInfo, RestRequest req, RestResponse res) throws RestException {
+
+									int rc = super.invoke(pathInfo, req, res);
+									if (rc != SC_OK)
+										return rc;
+
+									final Object o = res.getOutput();
+
+									if ("GET".equals(req.getMethod())) {
+										res.setOutput(getMethodInfo(remoteableMethods.values()));
+										return SC_OK;
+
+									} else if ("POST".equals(req.getMethod())) {
+										if (pathInfo.indexOf('/') != -1)
+											pathInfo = pathInfo.substring(pathInfo.lastIndexOf('/')+1);
+										pathInfo = urlDecode(pathInfo);
+										java.lang.reflect.Method m = remoteableMethods.get(pathInfo);
+										if (m != null) {
+											try {
+												// Parse the args and invoke the method.
+												Parser p = req.getBody().getParser();
+												Object input = p.isReaderParser() ? req.getReader() : req.getInputStream();
+												Object output = m.invoke(o, p.parseArgs(input, m.getGenericParameterTypes()));
+												res.setOutput(output);
+												return SC_OK;
+											} catch (Exception e) {
+												throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+											}
+										}
+									}
+									return SC_NOT_FOUND;
+								}
+							};
+
+							_javaRestMethods.put(method.getName(), sm);
+							addToRouter(routers, "GET", sm);
+							addToRouter(routers, "POST", sm);
+
+						} else {
+							_javaRestMethods.put(method.getName(), sm);
+							addToRouter(routers, httpMethod, sm);
+						}
+					} catch (RestServletException e) {
+						throw new RestServletException("Problem occurred trying to serialize methods on class {0}, methods={1}", this.getClass().getName(), JsonSerializer.DEFAULT_LAX.serialize(methodsFound)).initCause(e);
+					}
+				}
+			}
+
+			for (Method m : ClassUtils.getAllMethods(resource.getClass(), true)) {
+				if (ClassUtils.isPublic(m) && m.isAnnotationPresent(RestHook.class)) {
+					HookEvent he = m.getAnnotation(RestHook.class).value();
+					String sig = ClassUtils.getMethodSignature(m);
+					switch(he) {
+						case PRE_CALL: {
+							if (! _preCallMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_preCallMethods.put(sig, m);
+								_preCallMethodParams.add(findParams(m, false, null, true));
+							}
+							break;
+						}
+						case POST_CALL: {
+							if (! _postCallMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_postCallMethods.put(sig, m);
+								_postCallMethodParams.add(findParams(m, false, null, true));
+							}
+							break;
+						}
+						case START_CALL: {
+							if (! _startCallMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_startCallMethods.put(sig, m);
+								_startCallMethodParams.add(m.getParameterTypes());
+								ClassUtils.assertArgsOfType(m, HttpServletRequest.class, HttpServletResponse.class);
+							}
+							break;
+						}
+						case END_CALL: {
+							if (! _endCallMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_endCallMethods.put(sig, m);
+								_endCallMethodParams.add(m.getParameterTypes());
+								ClassUtils.assertArgsOfType(m, HttpServletRequest.class, HttpServletResponse.class);
+							}
+							break;
+						}
+						case POST_INIT: {
+							if (! _postInitMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_postInitMethods.put(sig, m);
+								_postInitMethodParams.add(m.getParameterTypes());
+								ClassUtils.assertArgsOfType(m, RestContext.class);
+							}
+							break;
+						}
+						case POST_INIT_CHILD_FIRST: {
+							if (! _postInitChildFirstMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_postInitChildFirstMethods.put(sig, m);
+								_postInitChildFirstMethodParams.add(m.getParameterTypes());
+								ClassUtils.assertArgsOfType(m, RestContext.class);
+							}
+							break;
+						}
+						case DESTROY: {
+							if (! _destroyMethods.containsKey(sig)) {
+								Visibility.setAccessible(m);
+								_destroyMethods.put(sig, m);
+								_destroyMethodParams.add(m.getParameterTypes());
+								ClassUtils.assertArgsOfType(m, RestContext.class);
+							}
+							break;
+						}
+						default: // Ignore INIT
+					}
+				}
+			}
+
+			this.callMethods = Collections.unmodifiableMap(_javaRestMethods);
+			this.preCallMethods = _preCallMethods.values().toArray(new Method[_preCallMethods.size()]);
+			this.postCallMethods = _postCallMethods.values().toArray(new Method[_postCallMethods.size()]);
+			this.startCallMethods = _startCallMethods.values().toArray(new Method[_startCallMethods.size()]);
+			this.endCallMethods = _endCallMethods.values().toArray(new Method[_endCallMethods.size()]);
+			this.postInitMethods = _postInitMethods.values().toArray(new Method[_postInitMethods.size()]);
+			this.postInitChildFirstMethods = _postInitChildFirstMethods.values().toArray(new Method[_postInitChildFirstMethods.size()]);
+			this.destroyMethods = _destroyMethods.values().toArray(new Method[_destroyMethods.size()]);
+			this.preCallMethodParams = _preCallMethodParams.toArray(new RestParam[_preCallMethodParams.size()][]);
+			this.postCallMethodParams = _postCallMethodParams.toArray(new RestParam[_postCallMethodParams.size()][]);
+			this.startCallMethodParams = _startCallMethodParams.toArray(new Class[_startCallMethodParams.size()][]);
+			this.endCallMethodParams = _endCallMethodParams.toArray(new Class[_endCallMethodParams.size()][]);
+			this.postInitMethodParams = _postInitMethodParams.toArray(new Class[_postInitMethodParams.size()][]);
+			this.postInitChildFirstMethodParams = _postInitChildFirstMethodParams.toArray(new Class[_postInitChildFirstMethodParams.size()][]);
+			this.destroyMethodParams = _destroyMethodParams.toArray(new Class[_destroyMethodParams.size()][]);
+
+			Map<String,CallRouter> _callRouters = new LinkedHashMap<String,CallRouter>();
+			for (CallRouter.Builder crb : routers.values())
+				_callRouters.put(crb.getHttpMethodName(), crb.build());
+			this.callRouters = Collections.unmodifiableMap(_callRouters);
+
+			// Initialize our child resources.
+			resourceResolver = resolve(resource, RestResourceResolver.class, b.resourceResolver);
+			for (Object o : config.childResources) {
+				String path = null;
+				Object r = null;
+				if (o instanceof Pair) {
+					Pair<String,Object> p = (Pair<String,Object>)o;
+					path = p.first();
+					r = p.second();
+				} else if (o instanceof Class<?>) {
+					Class<?> c = (Class<?>)o;
+					// Don't allow specifying yourself as a child.  Causes an infinite loop.
+					if (c == config.resourceClass)
+						continue;
+					r = c;
+				} else {
+					r = o;
+				}
+
+				RestConfig childConfig = null;
+
+				if (o instanceof Class) {
+					Class<?> oc = (Class<?>)o;
+					childConfig = new RestConfig(config.inner, oc, this);
+					r = resourceResolver.resolve(oc, childConfig);
+				} else {
+					r = o;
+					childConfig = new RestConfig(config.inner, o.getClass(), this);
+				}
+
+				childConfig.init(r);
+				if (r instanceof RestServlet)
+					((RestServlet)r).innerInit(childConfig);
+				RestContext rc2 = new RestContext(r, servletContext, childConfig);
+				if (r instanceof RestServlet)
+					((RestServlet)r).setContext(rc2);
+				path = childConfig.path;
+				childResources.put(path, rc2);
+			}
+
+			callHandler = config.callHandler == null ? new RestCallHandler(this) : resolve(resource, RestCallHandler.class, config.callHandler, this);
+			infoProvider = config.infoProvider == null ? new RestInfoProvider(this) : resolve(resource, RestInfoProvider.class, config.infoProvider, this);
+
+		} catch (RestException e) {
+			_initException = e;
+			throw e;
+		} catch (Exception e) {
+			_initException = new RestException(SC_INTERNAL_SERVER_ERROR, e);
+			throw e;
+		} finally {
+			initException = _initException;
+		}
+	}
+
+	private static void addToRouter(Map<String, CallRouter.Builder> routers, String httpMethodName, CallMethod cm) throws RestServletException {
+		if (! routers.containsKey(httpMethodName))
+			routers.put(httpMethodName, new CallRouter.Builder(httpMethodName));
+		routers.get(httpMethodName).add(cm);
+	}
+
+	private static class Builder {
+
+		boolean allowHeaderParams, allowBodyParam, renderResponseStackTraces, useStackTraceHashes;
+		VarResolver varResolver;
+		ConfigFile configFile;
+		ObjectMap properties;
+		Class<?>[] beanFilters;
+		Class<?>[] pojoSwaps;
+		Map<Class<?>,RestParam> paramResolvers = new HashMap<Class<?>,RestParam>();
+		SerializerGroup serializers;
+		ParserGroup parsers;
+		UrlEncodingSerializer urlEncodingSerializer;
+		UrlEncodingParser urlEncodingParser;
+		EncoderGroup encoders;
+		String clientVersionHeader = "", defaultCharset, paramFormat, htmlHeader, htmlNav, htmlAside, htmlStyle,
+				htmlStylesheet, htmlScript, htmlFooter, htmlNoResultsMessage;
+		String[] htmlLinks;
+		boolean htmlNoWrap;
+		HtmlDocTemplate htmlTemplate;
+
+		List<MediaType> supportedContentTypes, supportedAcceptTypes;
+		Map<String,String> defaultRequestHeaders = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
+		Map<String,Object> defaultResponseHeaders;
+		BeanContext beanContext;
+		List<RestConverter> converters = new ArrayList<RestConverter>();
+		List<RestGuard> guards = new ArrayList<RestGuard>();
+		List<ResponseHandler> responseHandlers = new ArrayList<ResponseHandler>();
+		MimetypesFileTypeMap mimetypesFileTypeMap;
+		StreamResource favIcon;
+		Map<String,String> staticFilesMap;
+		String[] staticFilesPrefixes;
+		MessageBundle messageBundle;
+		Set<String> allowMethodParams = new LinkedHashSet<String>();
+		RestLogger logger;
+		String fullPath;
+		Map<String,Widget> htmlWidgets;
+		Object resourceResolver;
+		String contextPath;
+
+		@SuppressWarnings("unchecked")
+		private Builder(Object resource, ServletContext ctx, RestConfig sc) throws Exception {
+
+			PropertyStore ps = sc.createPropertyStore();
+
+			LinkedHashMap<Class<?>,RestResource> restResourceAnnotationsChildFirst = findAnnotationsMap(RestResource.class, resource.getClass());
+
+			allowHeaderParams = ps.getProperty(REST_allowHeaderParams, boolean.class, true);
+			allowBodyParam = ps.getProperty(REST_allowBodyParam, boolean.class, true);
+			renderResponseStackTraces = ps.getProperty(REST_renderResponseStackTraces, boolean.class, false);
+			useStackTraceHashes = ps.getProperty(REST_useStackTraceHashes, boolean.class, true);
+			defaultCharset = ps.getProperty(REST_defaultCharset, String.class, "utf-8");
+			paramFormat = ps.getProperty(REST_paramFormat, String.class, "");
+			resourceResolver = ps.getProperty(REST_resourceResolver, Object.class, ctx == null ? null : ctx.getAttribute(REST_resourceResolver));
+			if (resourceResolver == null)
+				resourceResolver = sc.resourceResolver;
+
+			for (String m : split(ps.getProperty(REST_allowMethodParam, String.class, "")))
+				if (m.equals("true"))  // For backwards compatibility when this was a boolean field.
+					allowMethodParams.add("*");
+				else
+					allowMethodParams.add(m.toUpperCase());
+
+			varResolver = sc.varResolverBuilder
+				.vars(FileVar.class, LocalizationVar.class, RequestVar.class, SerializedRequestAttrVar.class, ServletInitParamVar.class, UrlVar.class, UrlEncodeVar.class, WidgetVar.class)
+				.build()
+			;
+			configFile = sc.configFile.getResolving(this.varResolver);
+			properties = sc.properties;
+			Collections.reverse(sc.beanFilters);
+			Collections.reverse(sc.pojoSwaps);
+			beanFilters = toObjectArray(sc.beanFilters, Class.class);
+			pojoSwaps = toObjectArray(sc.pojoSwaps, Class.class);
+
+			for (Class<?> c : sc.paramResolvers) {
+				RestParam rp = newInstanceFromOuter(resource, RestParam.class, c);
+				paramResolvers.put(rp.forClass(), rp);
+			}
+
+			clientVersionHeader = sc.clientVersionHeader;
+
+			// Find resource resource bundle location.
+			for (Map.Entry<Class<?>,RestResource> e : restResourceAnnotationsChildFirst.entrySet()) {
+				Class<?> c = e.getKey();
+				RestResource r = e.getValue();
+				if (! r.messages().isEmpty()) {
+					if (messageBundle == null)
+						messageBundle = new MessageBundle(c, r.messages());
+					else
+						messageBundle.addSearchPath(c, r.messages());
+				}
+			}
+
+			if (messageBundle == null)
+				messageBundle = new MessageBundle(resource.getClass(), "");
+
+			ps.addBeanFilters(beanFilters).addPojoSwaps(pojoSwaps).setProperties(properties);
+
+			serializers = sc.serializers.beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).listener(sc.serializerListener).build();
+			parsers = sc.parsers.beanFilters(beanFilters).pojoSwaps(pojoSwaps).properties(properties).listener(sc.parserListener).build();
+			urlEncodingSerializer = new UrlEncodingSerializer(ps);
+			urlEncodingParser = new UrlEncodingParser(ps);
+			encoders = sc.encoders.build();
+			supportedContentTypes = sc.supportedContentTypes != null ? sc.supportedContentTypes : serializers.getSupportedMediaTypes();
+			supportedAcceptTypes = sc.supportedAcceptTypes != null ? sc.supportedAcceptTypes : parsers.getSupportedMediaTypes();
+			defaultRequestHeaders.putAll(sc.defaultRequestHeaders);
+			defaultResponseHeaders = Collections.unmodifiableMap(new LinkedHashMap<String,Object>(sc.defaultResponseHeaders));
+			beanContext = ps.getBeanContext();
+			contextPath = sc.contextPath;
+
+			for (Object o : sc.converters)
+				converters.add(resolve(resource, RestConverter.class, o));
+
+			for (Object o : sc.guards)
+				guards.add(resolve(resource, RestGuard.class, o));
+
+			for (Object o : sc.responseHandlers)
+				responseHandlers.add(resolve(resource, ResponseHandler.class, o));
+
+			mimetypesFileTypeMap = sc.mimeTypes;
+
+			VarResolver vr = sc.getVarResolverBuilder().build();
+
+			if (sc.favIcon != null) {
+				Object o = sc.favIcon;
+				InputStream is = null;
+				if (o instanceof Pair) {
+					Pair<Class<?>,String> p = (Pair<Class<?>,String>)o;
+					is = ReflectionUtils.getResource(p.first(), vr.resolve(p.second()));
+				} else {
+					is = toInputStream(o);
+				}
+				if (is != null)
+					favIcon = new StreamResource(MediaType.forString("image/x-icon"), is);
+			}
+
+			staticFilesMap = new LinkedHashMap<String,String>();
+			if (sc.staticFiles != null) {
+				for (Object o : sc.staticFiles) {
+					if (o instanceof Pair) {
+						Pair<Class<?>,String> p = (Pair<Class<?>,String>)o;
+						// TODO - Currently doesn't take parent class location into account.
+						staticFilesMap.putAll(JsonParser.DEFAULT.parse(vr.resolve(p.second()), LinkedHashMap.class));
+					} else {
+						throw new RuntimeException("TODO");
+					}
+				}
+			}
+			staticFilesPrefixes = staticFilesMap.keySet().toArray(new String[0]);
+
+			logger = sc.logger == null ? new RestLogger.NoOp() : resolve(resource, RestLogger.class, sc.logger);
+
+			fullPath = (sc.parentContext == null ? "" : (sc.parentContext.fullPath + '/')) + sc.path;
+
+			this.htmlWidgets = new LinkedHashMap<String,Widget>();
+			for (Class<? extends Widget> wc : sc.htmlWidgets) {
+				Widget w = resolve(resource, Widget.class, wc);
+				this.htmlWidgets.put(w.getName(), w);
+			}
+
+			htmlHeader = sc.htmlHeader;
+			htmlLinks = sc.htmlLinks;
+			htmlNav = sc.htmlNav;
+			htmlAside = sc.htmlAside;
+			htmlStyle = sc.htmlStyle;
+			htmlStylesheet = sc.htmlStylesheet;
+			htmlScript = sc.htmlScript;
+			htmlFooter = sc.htmlFooter;
+			htmlNoWrap = sc.htmlNoWrap;
+			htmlNoResultsMessage = sc.htmlNoResultsMessage;
+			htmlTemplate = resolve(resource, HtmlDocTemplate.class, sc.htmlTemplate);
+		}
+	}
+
+	/**
+	 * Returns the resource resolver associated with this context.
+	 *
+	 * <p>
+	 * The resource resolver is used for instantiating child resource classes.
+	 *
+	 * <p>
+	 * Unless overridden via the {@link RestResource#resourceResolver()} annotation or the {@link RestConfig#setResourceResolver(Class)}
+	 * method, this value is always inherited from parent to child.
+	 * This allows a single resource resolver to be passed in to the top-level servlet to handle instantiation of all
+	 * child resources.
+	 *
+	 * @return The resource resolver associated with this context.
+	 */
+	protected RestResourceResolver getResourceResolver() {
+		return resourceResolver;
+	}
+
+	/**
+	 * Returns the variable resolver for this servlet.
+	 *
+	 * <p>
+	 * Variable resolvers are used to replace variables in property values.
+	 * They can be nested arbitrarily deep.
+	 * They can also return values that themselves contain other variables.
+	 *
+	 * <h6 class='figure'>Example:</h6>
+	 * <p class='bcode'>
+	 * 	<ja>@RestResource</ja>(
+	 * 		messages=<js>"nls/Messages"</js>,
+	 * 		properties={
+	 * 			<ja>@Property</ja>(name=<js>"title"</js>,value=<js>"$L{title}"</js>),  <jc>// Localized variable in Messages.properties</jc>
+	 * 			<ja>@Property</ja>(name=<js>"javaVendor"</js>,value=<js>"$S{java.vendor,Oracle}"</js>),  <jc>// System property with default value</jc>
+	 * 			<ja>@Property</ja>(name=<js>"foo"</js>,value=<js>"bar"</js>),
+	 * 			<ja>@Property</ja>(name=<js>"bar"</js>,value=<js>"baz"</js>),
+	 * 			<ja>@Property</ja>(name=<js>"v1"</js>,value=<js>"$R{foo}"</js>),  <jc>// Request variable.  value="bar"</jc>
+	 * 			<ja>@Property</ja>(name=<js>"v2"</js>,value=<js>"$R{$R{foo}}"</js>)  <jc>// Nested request variable. value="baz"</jc>
+	 * 		}
+	 * 	)
+	 * 	<jk>public class</jk> MyRestResource <jk>extends</jk> RestServletDefault {
+	 * </p>
+	 *
+	 * <p>
+	 * A typical usage pattern involves using variables inside the {@link HtmlDoc} annotation:
+	 * <p class='bcode'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		name=<js>"GET"</js>, path=<js>"/{name}/*"</js>,
+	 * 		htmldoc=@HtmlDoc(
+	 * 			links={
+	 * 				<js>"up: $R{requestParentURI}"</js>,
+	 * 				<js>"options: servlet:/?method=OPTIONS"</js>,
+	 * 				<js>"editLevel: servlet:/editLevel?logger=$A{attribute.name, OFF}"</js>
+	 * 			}
+	 * 			header={
+	 * 				<js>"&lt;h1&gt;$L{MyLocalizedPageTitle}&lt;/h1&gt;"</js>
+	 * 			},
+	 * 			aside={
+	 * 				<js>"$F{resources/AsideText.html}"</js>
+	 * 			}
+	 * 		)
+	 * 	)
+	 * 	<jk>public</jk> LoggerEntry getLogger(RestRequest req, <ja>@Path</ja> String name) <jk>throws</jk> Exception {
+	 * </p>
+	 *
+	 * <p>
+	 * The following is the default list of supported variables:
+	 * <ul>
+	 * 	<li><code>$C{key[,defaultValue]}</code> - Config file entry. See {@link ConfigFileVar}.
+	 * 	<li><code>$E{envVar[,defaultValue]}</code> - Environment variable. See {@link EnvVariablesVar}.
+	 * 	<li><code>$F{path[,defaultValue]}</code> - File resource. See {@link FileVar}.
+	 * 	<li><code>$I{name[,defaultValue]}</code> - Servlet init parameter. See {@link ServletInitParamVar}.
+	 * 	<li><code>$L{key[,args...]}</code> - Localized message. See {@link LocalizationVar}.
+	 * 	<li><code>$R{key[,args...]}</code> - Request variable. See {@link RequestVar}.
+	 * 	<li><code>$S{systemProperty[,defaultValue]}</code> - System property. See {@link SystemPropertiesVar}.
+	 * 	<li><code>$SA{contentType,key[,defaultValue]}</code> - Serialized request attribute. See {@link SerializedRequestAttrVar}.
+	 * 	<li><code>$U{uri}</code> - URI resolver. See {@link UrlVar}.
+	 * 	<li><code>$UE{uriPart}</code> - URL-Encoder. See {@link UrlEncodeVar}.
+	 * 	<li><code>$W{widgetName}</code> - HTML widget variable. See {@link WidgetVar}.
+	 * </ul>
+	 *
+	 * <p>
+	 * The following syntax variables are also provided:
+	 * <ul>
+	 * 	<li><code>$IF{booleanArg,thenValue[,elseValue]}</code> - If/else variable. See {@link IfVar}.
+	 * 	<li><code>$SW{stringArg(,pattern,thenValue)+[,elseValue]}</code> - Switch variable. See {@link SwitchVar}.
+	 * </ul>
+	 *
+	 * <p>
+	 * The list of variables can be extended using the {@link RestConfig#addVars(Class...)} method.
+	 * For example, this is used to add support for the Args and Manifest-File variables in the microservice
+	 * <code>Resource</code> class.
+	 *
+	 * @return The var resolver in use by this resource.
+	 */
+	public VarResolver getVarResolver() {
+		return varResolver;
+	}
+
+	/**
+	 * Returns the config file associated with this servlet.
+	 *
+	 * <p>
+	 * The config file is identified via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#config() @RestResource.config()} annotation.
+	 * 	<li>{@link RestConfig#setConfigFile(ConfigFile)} method.
+	 * </ul>
+	 *
+	 * @return The resolving config file associated with this servlet.  Never <jk>null</jk>.
+	 */
+	public ConfigFile getConfigFile() {
+		return configFile;
+	}
+
+	/**
+	 * Resolve a static resource file.
+	 *
+	 * <p>
+	 * The location of static resources are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#staticFiles() @RestResource.staticFiles()} annotation.
+	 * 	<li>{@link RestConfig#addStaticFiles(Class, String)} method.
+	 * </ul>
+	 *
+	 * @param pathInfo The unencoded path info.
+	 * @return The resource, or <jk>null</jk> if the resource could not be resolved.
+	 * @throws IOException
+	 */
+	public StreamResource resolveStaticFile(String pathInfo) throws IOException {
+		if (! staticFilesCache.containsKey(pathInfo)) {
+			String p = urlDecode(trimSlashes(pathInfo));
+			if (p.indexOf("..") != -1)
+				throw new RestException(SC_NOT_FOUND, "Invalid path");
+			for (Map.Entry<String,String> e : staticFilesMap.entrySet()) {
+				String key = trimSlashes(e.getKey());
+				if (p.startsWith(key)) {
+					String remainder = (p.equals(key) ? "" : p.substring(key.length()));
+					if (remainder.isEmpty() || remainder.startsWith("/")) {
+						String p2 = trimSlashes(e.getValue()) + remainder;
+						InputStream is = getResource(p2, null);
+						if (is != null) {
+							try {
+								int i = p2.lastIndexOf('/');
+								String name = (i == -1 ? p2 : p2.substring(i+1));
+								String mediaType = mimetypesFileTypeMap.getContentType(name);
+								ObjectMap headers = new ObjectMap().append("Cache-Control", "max-age=86400, public");
+								staticFilesCache.put(pathInfo, new StreamResource(MediaType.forString(mediaType), headers, is));
+								return staticFilesCache.get(pathInfo);
+							} finally {
+								is.close();
+							}
+						}
+					}
+				}
+			}
+		}
+		return staticFilesCache.get(pathInfo);
+	}
+
+	/**
+	 * Same as {@link Class#getResourceAsStream(String)} except if it doesn't find the resource on this class, searches
+	 * up the parent hierarchy chain.
+	 *
+	 * <p>
+	 * If the resource cannot be found in the classpath, then an attempt is made to look in the JVM working directory.
+	 *
+	 * <p>
+	 * If the <code>locale</code> is specified, then we look for resources whose name matches that locale.
+	 * For example, if looking for the resource <js>"MyResource.txt"</js> for the Japanese locale, we will look for
+	 * files in the following order:
+	 * <ol>
+	 * 	<li><js>"MyResource_ja_JP.txt"</js>
+	 * 	<li><js>"MyResource_ja.txt"</js>
+	 * 	<li><js>"MyResource.txt"</js>
+	 * </ol>
+	 *
+	 * @param name The resource name.
+	 * @param locale Optional locale.
+	 * @return An input stream of the resource, or <jk>null</jk> if the resource could not be found.
+	 * @throws IOException
+	 */
+	protected InputStream getResource(String name, Locale locale) throws IOException {
+		return resourceFinder.getResourceAsStream(name, locale);
+	}
+
+	/**
+	 * Reads the input stream from {@link #getResource(String, Locale)} into a String.
+	 *
+	 * @param name The resource name.
+	 * @param locale Optional locale.
+	 * @return The contents of the stream as a string, or <jk>null</jk> if the resource could not be found.
+	 * @throws IOException If resource could not be found.
+	 */
+	public String getResourceAsString(String name, Locale locale) throws IOException {
+		return resourceFinder.getResourceAsString(name, locale);
+	}
+
+	/**
+	 * Reads the input stream from {@link #getResource(String, Locale)} and parses it into a POJO using the parser
+	 * matched by the specified media type.
+	 *
+	 * <p>
+	 * Useful if you want to load predefined POJOs from JSON files in your classpath.
+	 *
+	 * @param c The class type of the POJO to create.
+	 * @param mediaType The media type of the data in the stream (e.g. <js>"text/json"</js>)
+	 * @param name The resource name (e.g. "htdocs/styles.css").
+	 * @param locale Optional locale.
+	 * @return The parsed resource, or <jk>null</jk> if the resource could not be found.
+	 * @throws IOException
+	 * @throws ServletException If the media type was unknown or the input could not be parsed into a POJO.
+	 */
+	public <T> T getResource(Class<T> c, MediaType mediaType, String name, Locale locale) throws IOException, ServletException {
+		InputStream is = getResource(name, locale);
+		if (is == null)
+			return null;
+		try {
+			Parser p = parsers.getParser(mediaType);
+			if (p != null) {
+				try {
+					if (p.isReaderParser())
+						return p.parse(new InputStreamReader(is, UTF8), c);
+					return p.parse(is, c);
+				} catch (ParseException e) {
+					throw new ServletException("Could not parse resource '' as media type '"+mediaType+"'.");
+				}
+			}
+			throw new ServletException("Unknown media type '"+mediaType+"'");
+		} catch (Exception e) {
+			throw new ServletException("Could not parse resource with name '"+name+"'", e);
+		}
+	}
+
+	/**
+	 * Returns the path for this resource as defined by the {@link RestResource#path()} annotation or
+	 * {@link RestConfig#setPath(String)} method concatenated with those on all parent classes.
+	 *
+	 * <p>
+	 * If path is not specified, returns <js>"/"</js>.
+	 *
+	 * <p>
+	 * Path always starts with <js>"/"</js>.
+	 *
+	 * @return The servlet path.
+	 */
+	public String getPath() {
+		return fullPath;
+	}
+
+	/**
+	 * The HTML page header contents.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#header()} annotation or {@link RestConfig#setHtmlHeader(String)} method.
+	 *
+	 * @return The HTML page header contents.
+	 */
+	public String getHtmlHeader() {
+		return htmlHeader;
+	}
+
+	/**
+	 * The HTML page nav section links.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#links()} annotation or {@link RestConfig#setHtmlLinks(String[])} method.
+	 *
+	 * @return The HTML page nav section links.
+	 */
+	public String[] getHtmlLinks() {
+		return htmlLinks;
+	}
+
+	/**
+	 * The HTML page nav section contents.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#nav()} annotation or {@link RestConfig#setHtmlNav(String)} method.
+	 *
+	 * @return The HTML page nav section contents.
+	 */
+	public String getHtmlNav() {
+		return htmlNav;
+	}
+
+	/**
+	 * The HTML page aside section contents.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#aside()} annotation or {@link RestConfig#setHtmlAside(String)} method.
+	 *
+	 * @return The HTML page aside section contents.
+	 */
+	public String getHtmlAside() {
+		return htmlAside;
+	}
+
+	/**
+	 * The HTML page footer section contents.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#footer()} annotation or {@link RestConfig#setHtmlFooter(String)} method.
+	 *
+	 * @return The HTML page footer section contents.
+	 */
+	public String getHtmlFooter() {
+		return htmlFooter;
+	}
+
+	/**
+	 * The HTML page stylesheet URL.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#stylesheet()} annotation or {@link RestConfig#setHtmlStylesheet(String)} method.
+	 *
+	 * @return The HTML page CSS URL.
+	 */
+	public String getHtmlStylesheet() {
+		return htmlStylesheet;
+	}
+
+	/**
+	 * The HTML page CSS contents.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#style()} annotation or {@link RestConfig#setHtmlStyle(String)} method.
+	 *
+	 * @return The HTML page CSS contents.
+	 */
+	public String getHtmlStyle() {
+		return htmlStyle;
+	}
+
+	/**
+	 * The HTML page Javascript contents.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#script()} annotation or {@link RestConfig#setHtmlScript(String)} method.
+	 *
+	 * @return The HTML page Javascript contents.
+	 */
+	public String getHtmlScript() {
+		return htmlScript;
+	}
+
+	/**
+	 * The HTML page nowrap setting.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#nowrap()} annotation or {@link RestConfig#setHtmlNoWrap(boolean)} method.
+	 *
+	 * @return The HTML page nowrap setting.
+	 */
+	public boolean getHtmlNoWrap() {
+		return htmlNoWrap;
+	}
+
+	/**
+	 * The HTML page template.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#template()} annotation or {@link RestConfig#setHtmlTemplate(Class)} method.
+	 *
+	 * @return The HTML page template.
+	 */
+	public HtmlDocTemplate getHtmlTemplate() {
+		return htmlTemplate;
+	}
+
+	/**
+	 * The HTML page no-results message.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#noResultsMessage()} annotation or {@link RestConfig#setHtmlNoResultsMessage(String)}
+	 * method.
+	 *
+	 * @return The HTML page no-results message.
+	 */
+	public String getHtmlNoResultsMessage() {
+		return htmlNoResultsMessage;
+	}
+
+	/**
+	 * The widgets used for resolving <js>"$W{...}"<js> variables.
+	 *
+	 * <p>
+	 * Defined by the {@link HtmlDoc#widgets()} annotation or {@link RestConfig#addHtmlWidget(Class)} method.
+	 *
+	 * @return The var resolver widgets as a map with keys being the name returned by {@link Widget#getName()}.
+	 */
+	public Map<String,Widget> getHtmlWidgets() {
+		return htmlWidgets;
+	}
+
+	/**
+	 * Returns the logger to use for this resource.
+	 *
+	 * <p>
+	 * The logger for a resource is defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#logger() @RestResource.logger()} annotation.
+	 * 	<li>{@link RestConfig#setLogger(Class)}/{@link RestConfig#setLogger(RestLogger)} methods.
+	 * </ul>
+	 *
+	 * @return The logger to use for this resource.  Never <jk>null</jk>.
+	 */
+	public RestLogger getLogger() {
+		return logger;
+	}
+
+	/**
+	 * Returns the resource bundle used by this resource.
+	 *
+	 * <p>
+	 * The resource bundle is defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#messages() @RestResource.messages()} annotation.
+	 * </ul>
+	 *
+	 * @return The resource bundle for this resource.  Never <jk>null</jk>.
+	 */
+	public MessageBundle getMessages() {
+		return msgs;
+	}
+
+	/**
+	 * Returns the REST information provider used by this resource.
+	 *
+	 * <p>
+	 * The information provider is defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#infoProvider() @RestResource.infoProvider()} annotation.
+	 * 	<li>{@link RestConfig#setInfoProvider(Class)}/{@link RestConfig#setInfoProvider(RestInfoProvider)} methods.
+	 * </ul>
+	 *
+	 * @return The information provider for this resource.  Never <jk>null</jk>.
+	 */
+	public RestInfoProvider getInfoProvider() {
+		return infoProvider;
+	}
+
+	/**
+	 * Returns the REST call handler used by this resource.
+	 *
+	 * <p>
+	 * The call handler is defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#callHandler() @RestResource.callHandler()} annotation.
+	 * 	<li>{@link RestConfig#setCallHandler(Class)}/{@link RestConfig#setCallHandler(RestCallHandler)} methods.
+	 * </ul>
+	 *
+	 * @return The call handler for this resource.  Never <jk>null</jk>.
+	 */
+	protected RestCallHandler getCallHandler() {
+		return callHandler;
+	}
+
+	/**
+	 * Returns a map of HTTP method names to call routers.
+	 *
+	 * @return A map with HTTP method names upper-cased as the keys, and call routers as the values.
+	 */
+	protected Map<String,CallRouter> getCallRouters() {
+		return callRouters;
+	}
+
+	/**
+	 * Returns the resource object.
+	 *
+	 * <p>
+	 * This is the instance of the class annotated with the {@link RestResource @RestResource} annotation, usually
+	 * an instance of {@link RestServlet}.
+	 *
+	 * @return The resource object.  Never <jk>null</jk>.
+	 */
+	public Object getResource() {
+		return resource;
+	}
+
+	/**
+	 * Returns the resource object as a {@link RestServlet}.
+	 *
+	 * @return
+	 * 	The resource object cast to {@link RestServlet}, or <jk>null</jk> if the resource doesn't subclass from
+	 * 	{@link RestServlet}
+	 */
+	public RestServlet getRestServlet() {
+		return resource instanceof RestServlet ? (RestServlet)resource : null;
+	}
+
+	/**
+	 * Throws a {@link RestException} if an exception occurred in the constructor of this object.
+	 *
+	 * @throws RestException The initialization exception wrapped in a {@link RestException}.
+	 */
+	protected void checkForInitException() throws RestException {
+		if (initException != null)
+			throw initException;
+	}
+
+	/**
+	 * Returns the parent resource context (if this resource was initialized from a parent).
+	 *
+	 * <p>
+	 * From this object, you can get access to the parent resource class itself using {@link #getResource()} or
+	 * {@link #getRestServlet()}
+	 *
+	 * @return The parent resource context, or <jk>null</jk> if there is no parent context.
+	 */
+	public RestContext getParentContext() {
+		return parentContext;
+	}
+
+	/**
+	 * Returns the {@link BeanContext} object used for parsing path variables and header values.
+	 *
+	 * @return The bean context used for parsing path variables and header values.
+	 */
+	public BeanContext getBeanContext() {
+		return beanContext;
+	}
+
+	/**
+	 * Returns the class-level properties associated with this servlet.
+	 *
+	 * <p>
+	 * Properties at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#properties() @RestResource.properties()} annotation.
+	 * 	<li>{@link RestConfig#setProperty(String, Object)}/{@link RestConfig#setProperties(Map)} methods.
+	 * </ul>
+	 *
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul>
+	 * 	<li>The returned {@code Map} is mutable.  Therefore, subclasses are free to override
+	 * 	or set additional initialization parameters in their {@code init()} method.
+	 * </ul>
+	 *
+	 * @return The resource properties as an {@link ObjectMap}.
+	 */
+	public ObjectMap getProperties() {
+		return properties;
+	}
+
+	/**
+	 * Returns the serializers registered with this resource.
+	 *
+	 * <p>
+	 * Serializers at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#serializers() @RestResource.serializers()} annotation.
+	 * 	<li>{@link RestConfig#addSerializers(Class...)}/{@link RestConfig#addSerializers(Serializer...)} methods.
+	 * </ul>
+	 *
+	 * @return The serializers registered with this resource.
+	 */
+	public SerializerGroup getSerializers() {
+		return serializers;
+	}
+
+	/**
+	 * Returns the parsers registered with this resource.
+	 *
+	 * <p>
+	 * Parsers at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#parsers() @RestResource.parsers()} annotation.
+	 * 	<li>{@link RestConfig#addParsers(Class...)}/{@link RestConfig#addParsers(Parser...)} methods.
+	 * </ul>
+	 *
+	 * @return The parsers registered with this resource.
+	 */
+	public ParserGroup getParsers() {
+		return parsers;
+	}
+
+	/**
+	 * Returns the servlet init parameter returned by {@link ServletConfig#getInitParameter(String)}.
+	 *
+	 * @param name The init parameter name.
+	 * @return The servlet init parameter, or <jk>null</jk> if not found.
+	 */
+	public String getServletInitParameter(String name) {
+		return config.getInitParameter(name);
+	}
+
+	/**
+	 * Returns the child resources associated with this servlet.
+	 *
+	 * @return
+	 * 	An unmodifiable map of child resources.
+	 * 	Keys are the {@link RestResource#path() @RestResource.path()} annotation defined on the child resource.
+	 */
+	public Map<String,RestContext> getChildResources() {
+		return Collections.unmodifiableMap(childResources);
+	}
+
+	/**
+	 * Returns the number of times this exception was thrown based on a hash of its stacktrace.
+	 *
+	 * @param e The exception to check.
+	 * @return
+	 * 	The number of times this exception was thrown, or <code>0</code> if <code>stackTraceHashes</code>
+	 * 	setting is not enabled.
+	 */
+	protected int getStackTraceOccurrence(Throwable e) {
+		if (! useStackTraceHashes)
+			return 0;
+		int h = e.hashCode();
+		stackTraceHashes.putIfAbsent(h, new AtomicInteger());
+		return stackTraceHashes.get(h).incrementAndGet();
+	}
+
+	/**
+	 * Returns the value of the {@link #REST_renderResponseStackTraces} setting.
+	 *
+	 * @return The value of the {@link #REST_renderResponseStackTraces} setting.
+	 */
+	protected boolean isRenderResponseStackTraces() {
+		return renderResponseStackTraces;
+	}
+
+	/**
+	 * Returns the value of the {@link #REST_allowHeaderParams} setting.
+	 *
+	 * @return The value of the {@link #REST_allowHeaderParams} setting.
+	 */
+	protected boolean isAllowHeaderParams() {
+		return allowHeaderParams;
+	}
+
+	/**
+	 * Returns the value of the {@link #REST_allowBodyParam} setting.
+	 *
+	 * @return The value of the {@link #REST_allowBodyParam} setting.
+	 */
+	protected boolean isAllowBodyParam() {
+		return allowBodyParam;
+	}
+
+	/**
+	 * Returns the value of the {@link #REST_defaultCharset} setting.
+	 *
+	 * @return The value of the {@link #REST_defaultCharset} setting.
+	 */
+	protected String getDefaultCharset() {
+		return defaultCharset;
+	}
+
+	/**
+	 * Returns the value of the {@link #REST_paramFormat} setting.
+	 *
+	 * @return The value of the {@link #REST_paramFormat} setting.
+	 */
+	protected String getParamFormat() {
+		return paramFormat;
+	}
+
+	/**
+	 * Returns the name of the client version header name used by this resource.
+	 *
+	 * <p>
+	 * The client version header is the name of the HTTP header on requests that identify a client version.
+	 *
+	 * <p>
+	 * The client version header is defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#clientVersionHeader() @RestResource.clientVersion()} annotation.
+	 * </ul>
+	 *
+	 * @return The name of the client version header used by this resource.  Never <jk>null</jk>.
+	 */
+	protected String getClientVersionHeader() {
+		return clientVersionHeader;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified <code>Method</code> GET parameter value can be used to override
+	 * the method name in the HTTP header.
+	 *
+	 * @param m The method name, upper-cased.
+	 * @return <jk>true</jk> if this resource allows the specified method to be overridden.
+	 */
+	protected boolean allowMethodParam(String m) {
+		return (! isEmpty(m) && (allowMethodParams.contains(m) || allowMethodParams.contains("*")));
+	}
+
+	/**
+	 * Returns the bean filters associated with this resource.
+	 *
+	 * <p>
+	 * Bean filters at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#beanFilters() @RestResource.beanFilters()} annotation.
+	 * 	<li>{@link RestConfig#addBeanFilters(Class...)} method.
+	 * </ul>
+	 *
+	 * @return The bean filters associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected Class<?>[] getBeanFilters() {
+		return beanFilters;
+	}
+
+	/**
+	 * Returns the POJO swaps associated with this resource.
+	 *
+	 * <p>
+	 * POJO swaps at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#pojoSwaps() @RestResource.pojoSwaps()} annotation.
+	 * 	<li>{@link RestConfig#addPojoSwaps(Class...)} method.
+	 * </ul>
+	 *
+	 * @return The POJO swaps associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected Class<?>[] getPojoSwaps() {
+		return pojoSwaps;
+	}
+
+	/**
+	 * Finds the {@link RestParam} instances to handle resolving objects on the calls to the specified Java method.
+	 *
+	 * @param method The Java method being called.
+	 * @param methodPlainParams Whether plain-params setting is specified.
+	 * @param pathPattern The parsed URL path pattern.
+	 * @param isPreOrPost Whether this is a <ja>@RestMethodPre</ja> or <ja>@RestMethodPost</ja>.
+	 * @return The array of resolvers.
+	 * @throws ServletException If an annotation usage error was detected.
+	 */
+	protected RestParam[] findParams(Method method, boolean methodPlainParams, UrlPathPattern pathPattern, boolean isPreOrPost) throws ServletException {
+
+		Type[] pt = method.getGenericParameterTypes();
+		Annotation[][] pa = method.getParameterAnnotations();
+		RestParam[] rp = new RestParam[pt.length];
+		int attrIndex = 0;
+
+		for (int i = 0; i < pt.length; i++) {
+
+			Type t = pt[i];
+			if (t instanceof Class) {
+				Class<?> c = (Class<?>)t;
+				rp[i] = paramResolvers.get(c);
+				if (rp[i] == null)
+					rp[i] = RestParamDefaults.STANDARD_RESOLVERS.get(c);
+			}
+
+			if (rp[i] == null) {
+				for (Annotation a : pa[i]) {
+					if (a instanceof Header)
+						rp[i] = new RestParamDefaults.HeaderObject((Header)a, t);
+					else if (a instanceof FormData)
+						rp[i] = new RestParamDefaults.FormDataObject(method, (FormData)a, t, methodPlainParams);
+					else if (a instanceof Query)
+						rp[i] = new RestParamDefaults.QueryObject(method, (Query)a, t, methodPlainParams);
+					else if (a instanceof HasFormData)
+						rp[i] = new RestParamDefaults.HasFormDataObject(method, (HasFormData)a, t);
+					else if (a instanceof HasQuery)
+						rp[i] = new RestParamDefaults.HasQueryObject(method, (HasQuery)a, t);
+					else if (a instanceof Body)
+						rp[i] = new RestParamDefaults.BodyObject(t);
+					else if (a instanceof org.apache.juneau.rest.annotation.Method)
+						rp[i] = new RestParamDefaults.MethodObject(method, t);
+					else if (a instanceof PathRemainder)
+						rp[i] = new RestParamDefaults.PathRemainderObject(method, t);
+					else if (a instanceof Properties)
+						rp[i] = new RestParamDefaults.PropsObject(method, t);
+					else if (a instanceof Messages)
+						rp[i] = new RestParamDefaults.MessageBundleObject();
+				}
+			}
+
+			if (rp[i] == null) {
+
+				if (isPreOrPost)
+					throw new RestServletException("Invalid parameter specified for method ''{0}'' at index position {1}", method, i);
+
+				Path p = null;
+				for (Annotation a : pa[i])
+					if (a instanceof Path)
+						p = (Path)a;
+
+				String name = (p == null ? "" : firstNonEmpty(p.name(), p.value()));
+
+				if (isEmpty(name)) {
+					int idx = attrIndex++;
+					String[] vars = pathPattern.getVars();
+					if (vars.length <= idx)
+						throw new RestServletException("Number of attribute parameters in method ''{0}'' exceeds the number of URL pattern variables.", method);
+
+					// Check for {#} variables.
+					String idxs = String.valueOf(idx);
+					for (int j = 0; j < vars.length; j++)
+						if (isNumeric(vars[j]) && vars[j].equals(idxs))
+							name = vars[j];
+
+					if (isEmpty(name))
+						name = pathPattern.getVars()[idx];
+				}
+				rp[i] = new RestParamDefaults.PathParameterObject(name, t);
+			}
+		}
+
+		return rp;
+	}
+
+	/*
+	 * Calls all @RestHook(PRE) methods.
+	 */
+	void preCall(RestRequest req, RestResponse res) throws RestException {
+		for (int i = 0; i < preCallMethods.length; i++)
+			preOrPost(resource, preCallMethods[i], preCallMethodParams[i], req, res);
+	}
+
+	/*
+	 * Calls all @RestHook(POST) methods.
+	 */
+	void postCall(RestRequest req, RestResponse res) throws RestException {
+		for (int i = 0; i < postCallMethods.length; i++)
+			preOrPost(resource, postCallMethods[i], postCallMethodParams[i], req, res);
+	}
+
+	private static void preOrPost(Object resource, Method m, RestParam[] mp, RestRequest req, RestResponse res) throws RestException {
+		if (m != null) {
+			Object[] args = new Object[mp.length];
+			for (int i = 0; i < mp.length; i++) {
+				try {
+					args[i] = mp[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}''.",
+						mp[i].getParamType().name(), mp[i].getName(), mp[i].getType(), m.getDeclaringClass().getName(), m.getName()
+					).initCause(e);
+				}
+			}
+			try {
+				m.invoke(resource, args);
+			} catch (RestException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new RestException(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()).initCause(e);
+			}
+		}
+	}
+
+	/*
+	 * Calls all @RestHook(START) methods.
+	 */
+	void startCall(HttpServletRequest req, HttpServletResponse res) {
+		for (int i = 0; i < startCallMethods.length; i++)
+			startOrFinish(resource, startCallMethods[i], startCallMethodParams[i], req, res);
+	}
+
+	/*
+	 * Calls all @RestHook(FINISH) methods.
+	 */
+	void finishCall(HttpServletRequest req, HttpServletResponse res) {
+		for (int i = 0; i < endCallMethods.length; i++)
+			startOrFinish(resource, endCallMethods[i], endCallMethodParams[i], req, res);
+	}
+
+	private static void startOrFinish(Object resource, Method m, Class<?>[] p, HttpServletRequest req, HttpServletResponse res) {
+		if (m != null) {
+			Object[] args = new Object[p.length];
+			for (int i = 0; i < p.length; i++) {
+				if (p[i] == HttpServletRequest.class)
+					args[i] = req;
+				else if (p[i] == HttpServletResponse.class)
+					args[i] = res;
+			}
+			try {
+				m.invoke(resource, args);
+			} catch (RestException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new RestException(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()).initCause(e);
+			}
+		}
+	}
+
+	/*
+	 * Calls all @RestHook(POST_INIT) methods.
+	 */
+	void postInit() throws ServletException {
+		for (int i = 0; i < postInitMethods.length; i++)
+			postInitOrDestroy(resource, postInitMethods[i], postInitMethodParams[i]);
+		for (RestContext childContext : this.childResources.values())
+			childContext.postInit();
+	}
+
+	/*
+	 * Calls all @RestHook(POST_INIT_CHILD_FIRST) methods.
+	 */
+	void postInitChildFirst() throws ServletException {
+		for (RestContext childContext : this.childResources.values())
+			childContext.postInitChildFirst();
+		for (int i = 0; i < postInitChildFirstMethods.length; i++)
+			postInitOrDestroy(resource, postInitChildFirstMethods[i], postInitChildFirstMethodParams[i]);
+	}
+
+	private void postInitOrDestroy(Object r, Method m, Class<?>[] p) {
+		if (m != null) {
+			Object[] args = new Object[p.length];
+			for (int i = 0; i < p.length; i++) {
+				if (p[i] == RestContext.class)
+					args[i] = this;
+				else if (p[i] == RestConfig.class)
+					args[i] = this.config;
+				else if (p[i] == ServletConfig.class)
+					args[i] = this.config.inner;
+			}
+			try {
+				m.invoke(r, args);
+			} catch (RestException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new RestException(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage()).initCause(e);
+			}
+		}
+	}
+
+
+	/**
+	 * Returns the URL-encoding parser associated with this resource.
+	 *
+	 * @return The URL-encoding parser associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected UrlEncodingParser getUrlEncodingParser() {
+		return urlEncodingParser;
+	}
+
+	/**
+	 * Returns the URL-encoding serializer associated with this resource.
+	 *
+	 * @return The URL-encoding serializer associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected UrlEncodingSerializer getUrlEncodingSerializer() {
+		return urlEncodingSerializer;
+	}
+
+	/**
+	 * Returns the encoders associated with this resource.
+	 *
+	 * <p>
+	 * Encoders are used to provide various types of encoding such as <code>gzip</code> encoding.
+	 *
+	 * <p>
+	 * Encoders at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#encoders() @RestResource.encoders()} annotation.
+	 * 	<li>{@link RestConfig#addEncoders(Class...)}/{@link RestConfig#addEncoders(org.apache.juneau.encoders.Encoder...)}
+	 * 		methods.
+	 * </ul>
+	 *
+	 * @return The encoders associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected EncoderGroup getEncoders() {
+		return encoders;
+	}
+
+	/**
+	 * Returns the explicit list of supported accept types for this resource.
+	 *
+	 * <p>
+	 * By default, this is simply the list of accept types supported by the registered parsers, but
+	 * can be overridden via the {@link RestConfig#setSupportedAcceptTypes(MediaType...)}/{@link RestConfig#setSupportedAcceptTypes(String...)}
+	 * methods.
+	 *
+	 * @return The supported <code>Accept</code> header values for this resource.  Never <jk>null</jk>.
+	 */
+	protected MediaType[] getSupportedAcceptTypes() {
+		return supportedAcceptTypes;
+	}
+
+	/**
+	 * Returns the explicit list of supported content types for this resource.
+	 *
+	 * <p>
+	 * By default, this is simply the list of content types supported by the registered serializers, but can be
+	 * overridden via the {@link RestConfig#setSupportedContentTypes(MediaType...)}/{@link RestConfig#setSupportedContentTypes(String...)}
+	 * methods.
+	 *
+	 * @return The supported <code>Content-Type</code> header values for this resource.  Never <jk>null</jk>.
+	 */
+	protected MediaType[] getSupportedContentTypes() {
+		return supportedContentTypes;
+	}
+
+	/**
+	 * Returns the default request headers for this resource.
+	 *
+	 * <p>
+	 * These are headers automatically added to requests if not present.
+	 *
+	 * <p>
+	 * Default request headers are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#defaultRequestHeaders() @RestResource.defaultRequestHeaders()} annotation.
+	 * 	<li>{@link RestConfig#addDefaultRequestHeader(String, Object)}/{@link RestConfig#addDefaultRequestHeaders(String...)} methods.
+	 * </ul>
+	 *
+	 * @return The default request headers for this resource.  Never <jk>null</jk>.
+	 */
+	protected Map<String,String> getDefaultRequestHeaders() {
+		return defaultRequestHeaders;
+	}
+
+	/**
+	 * Returns the default response headers for this resource.
+	 *
+	 * <p>
+	 * These are headers automatically added to responses if not otherwise specified during the request.
+	 *
+	 * <p>
+	 * Default response headers are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#defaultResponseHeaders() @RestResource.defaultResponseHeaders()} annotation.
+	 * 	<li>{@link RestConfig#addDefaultResponseHeader(String, Object)}/{@link RestConfig#addDefaultResponseHeaders(String...)}
+	 * 		methods.
+	 * </ul>
+	 *
+	 * @return The default response headers for this resource.  Never <jk>null</jk>.
+	 */
+	public Map<String,Object> getDefaultResponseHeaders() {
+		return defaultResponseHeaders;
+	}
+
+	/**
+	 * Returns the converters associated with this resource at the class level.
+	 *
+	 * <p>
+	 * Converters are used to 'convert' POJOs from one form to another before being passed of to the response handlers.
+	 *
+	 * <p>
+	 * Converters at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#converters() @RestResource.converters()} annotation.
+	 * 	<li>{@link RestConfig#addConverters(Class...)}/{@link RestConfig#addConverters(RestConverter...)} methods.
+	 * </ul>
+	 *
+	 * @return The converters associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected RestConverter[] getConverters() {
+		return converters;
+	}
+
+	/**
+	 * Returns the guards associated with this resource at the class level.
+	 *
+	 * <p>
+	 * Guards are used to restrict access to resources.
+	 *
+	 * <p>
+	 * Guards at the class level are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#guards() @RestResource.guards()} annotation.
+	 * 	<li>{@link RestConfig#addGuards(Class...)}/{@link RestConfig#addGuards(RestGuard...)} methods.
+	 * </ul>
+	 *
+	 * @return The guards associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected RestGuard[] getGuards() {
+		return guards;
+	}
+
+	/**
+	 * Returns the response handlers associated with this resource.
+	 *
+	 * <p>
+	 * Response handlers are used to convert POJOs returned by REST Java methods into actual HTTP responses.
+	 *
+	 * <p>
+	 * Response handlers are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#responseHandlers() @RestResource.responseHandlers()} annotation.
+	 * 	<li>{@link RestConfig#addResponseHandlers(Class...)}/{@link RestConfig#addResponseHandlers(ResponseHandler...)}
+	 * 		methods.
+	 * </ul>
+	 *
+	 * @return The response handlers associated with this resource.  Never <jk>null</jk>.
+	 */
+	protected ResponseHandler[] getResponseHandlers() {
+		return responseHandlers;
+	}
+
+	/**
+	 * Returns the media type for the specified file name.
+	 *
+	 * <p>
+	 * The list of MIME-type mappings can be augmented through the {@link RestConfig#addMimeTypes(String...)} method.
+	 * See that method for a description of predefined MIME-type mappings.
+	 *
+	 * @param name The file name.
+	 * @return The MIME-type, or <jk>null</jk> if it could not be determined.
+	 */
+	protected String getMediaTypeForName(String name) {
+		return mimetypesFileTypeMap.getContentType(name);
+	}
+
+	/**
+	 * Returns the favicon of the resource.
+	 *
+	 * <p>
+	 * This is the icon served up under <js>"/favicon.ico"</jk> recognized by browsers.
+	 *
+	 * <p>
+	 * The favicon is defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#favicon() @RestResource.favicon()} annotation.
+	 * 	<li>{@link RestConfig#setFavIcon(Object)}/{@link RestConfig#setFavIcon(Class, String)} methods.
+	 * </ul>
+	 *
+	 * @return The favicon of this resource.  Can be <jk>null</jk>.
+	 */
+	protected StreamResource getFavIcon() {
+		return favIcon;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified path refers to a static file.
+	 *
+	 * <p>
+	 * Static files are files pulled from the classpath and served up directly to the browser.
+	 *
+	 * <p>
+	 * Static files are defined via one of the following:
+	 * <ul>
+	 * 	<li>{@link RestResource#staticFiles() @RestResource.staticFiles()} annotation.
+	 * 	<li>{@link RestConfig#addStaticFiles(Class, String)} method.
+	 * </ul>
+	 *
+	 * @param p The URL path remainder after the servlet match.
+	 * @return <jk>true</jk> if the specified path refers to a static file.
+	 */
+	protected boolean isStaticFile(String p) {
+		return pathStartsWith(p, staticFilesPrefixes);
+	}
+
+	/**
+	 * Returns the REST Java methods defined in this resource.
+	 *
+	 * <p>
+	 * These are the methods annotated with the {@link RestMethod @RestMethod} annotation.
+	 *
+	 * @return A map of Java method names to call method objects.
+	 */
+	protected Map<String,CallMethod> getCallMethods() {
+		return callMethods;
+	}
+
+	/**
+	 * Calls {@link Servlet#destroy()} on any child resources defined on this resource.
+	 */
+	protected void destroy() {
+		for (int i = 0; i < destroyMethods.length; i++) {
+			try {
+				postInitOrDestroy(resource, destroyMethods[i], destroyMethodParams[i]);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		}
+
+		for (RestContext r : childResources.values()) {
+			r.destroy();
+			if (r.resource instanceof Servlet)
+				((Servlet)r.resource).destroy();
+		}
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this resource has any child resources associated with it.
+	 *
+	 * @return <jk>true</jk> if this resource has any child resources associated with it.
+	 */
+	protected boolean hasChildResources() {
+		return ! childResources.isEmpty();
+	}
+
+	/**
+	 * Returns the context of the child resource associated with the specified path.
+	 *
+	 * @param path The path of the child resource to resolve.
+	 * @return The resolved context, or <jk>null</jk> if it could not be resolved.
+	 */
+	protected RestContext getChildResource(String path) {
+		return childResources.get(path);
+	}
+
+	/**
+	 * Returns the context path of the resource if it's specified via the {@link RestResource#contextPath()} setting
+	 * on this or a parent resource.
+	 *
+	 * @return The {@link RestResource#contextPath()} setting value, or <jk>null</jk> if it's not specified.
+	 */
+	protected String getContextPath() {
+		if (contextPath != null)
+			return contextPath;
+		if (parentContext != null)
+			return parentContext.getContextPath();
+		return null;
+	}
+
+	//----------------------------------------------------------------------------------------------------
+	// Utility methods
+	//----------------------------------------------------------------------------------------------------
+
+	/**
+	 * Takes in an object of type T or a Class<T> and either casts or constructs a T.
+	 */
+	private static <T> T resolve(Object outer, Class<T> c, Object o, Object...cArgs) throws RestServletException {
+		try {
+			return ClassUtils.newInstanceFromOuter(outer, c, o, cArgs);
+		} catch (Exception e) {
+			throw new RestServletException("Exception occurred while constructing class ''{0}''", c).initCause(e);
+		}
+	}
+}

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

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestConverter.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,83 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.converters.*;
+import org.apache.juneau.serializer.*;
+
+/**
+ * REST method response converter.
+ *
+ * <p>
+ * Implements a filter mechanism for REST method calls that allows response objects to be converted to some other POJO
+ * after invocation of the REST method.
+ *
+ * <p>
+ * Converters are associated with REST methods through the {@link RestMethod#converters()} annotation.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode'>
+ * 	<jk>public class</jk> RequestEchoResource <jk>extends</jk> RestServlet {
+ *
+ * 		<jc>// GET request handler</jc>
+ * 		<ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/*"</js>, converters={Queryable.<jk>class</jk>,Traversable.<jk>class</jk>})
+ * 		<jk>public</jk> HttpServletRequest doGet(RestRequest req) {
+ * 			res.setTitle(<js>"Contents of HttpServletRequest object"</js>);
+ * 			<jk>return</jk> req;
+ * 		}
+ * 	}
+ * </p>
+ *
+ * <p>
+ * Converters can also be associated at the servlet level using the {@link RestResource#converters()} annotation.
+ * Applying converters at the resource level is equivalent to applying converters to each resource method individually.
+ *
+ * <h6 class='topic'>How to implement</h6>
+ *
+ * Implementers should simply implement the {@link #convert(RestRequest, Object, ClassMeta)} and return back a
+ * 'converted' object.
+ * It's up to the implementer to decide what this means.
+ *
+ * <p>
+ * Converters must implement a no-args constructor.
+ *
+ * <h6 class='topic'>Predefined converters</h6>
+ *
+ * The following converters are available by default.
+ * <ul class='spaced-list'>
+ * 	<li>
+ * 		{@link Traversable} - Allows URL additional path info to address individual elements in a POJO tree.
+ * 	<li>
+ * 		{@link Queryable} - Allows query/view/sort functions to be performed on POJOs.
+ * 	<li>
+ * 		{@link Introspectable} - Allows Java public methods to be invoked on the returned POJOs.
+ * </ul>
+ */
+public interface RestConverter {
+
+	/**
+	 * Performs post-call conversion on the specified response object.
+	 *
+	 * @param req The servlet request.
+	 * @param res The response object set by the REST method through the {@link RestResponse#setOutput(Object)} method.
+	 * @param cm
+	 * 	The {@link ClassMeta} on the object from the bean context of the servlet.
+	 * 	Can be used to check if the object has any filters.
+	 * @return The converted object.
+	 * @throws RestException Thrown if any errors occur during conversion.
+	 * @throws SerializeException
+	 */
+	public Object convert(RestRequest req, Object res, ClassMeta<?> cm) throws RestException, SerializeException;
+}

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

Added: release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java
==============================================================================
--- release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java (added)
+++ release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestException.java Fri Sep  8 23:25:34 2017
@@ -0,0 +1,165 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.lang.reflect.*;
+import java.text.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Exception thrown to trigger an error HTTP status.
+ *
+ * <p>
+ * REST methods on subclasses of {@link RestServlet} can throw this exception to trigger an HTTP status other than the
+ * automatically-generated <code>404</code>, <code>405</code>, and <code>500</code> statuses.
+ */
+public class RestException extends FormattedRuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	private final int status;
+	private int occurrence;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param status The HTTP status code.
+	 * @param msg The status message.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 */
+	public RestException(int status, String msg, Object...args) {
+		super(msg, args);
+		this.status = status;
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param status The HTTP status code.
+	 * @param cause The root exception.
+	 */
+	public RestException(int status, Throwable cause) {
+		this(status, cause.getLocalizedMessage());
+		initCause(cause);
+	}
+
+
+	/**
+	 * Sets the inner cause for this exception.
+	 *
+	 * @param cause The inner cause.
+	 * @return This object (for method chaining).
+	 */
+	@Override /* Throwable */
+	public synchronized RestException initCause(Throwable cause) {
+		super.initCause(cause);
+		return this;
+	}
+
+	/**
+	 * Returns the root cause of this exception.
+	 *
+	 * <p>
+	 * The root cause is the first exception in the init-cause parent chain that's not one of the following:
+	 * <ul>
+	 * 	<li>{@link RestException}
+	 * 	<li>{@link InvocationTargetException}
+	 * </ul>
+	 *
+	 * @return The root cause of this exception, or <jk>null</jk> if no root cause was found.
+	 */
+	public Throwable getRootCause() {
+		Throwable t = this;
+		while(t != null) {
+			t = t.getCause();
+			if (! (t instanceof RestException || t instanceof InvocationTargetException))
+				return t;
+		}
+		return null;
+	}
+
+	/**
+	 * Returns all error messages from all errors in this stack.
+	 *
+	 * <p>
+	 * Typically useful if you want to render all the error messages in the stack, but don't want to render all the
+	 * stack traces too.
+	 *
+	 * @param scrubForXssVulnerabilities
+	 * 	If <jk>true</jk>, replaces <js>'&lt;'</js>, <js>'&gt;'</js>, and <js>'&amp;'</js> characters with spaces.
+	 * @return All error messages from all errors in this stack.
+	 */
+	public String getFullStackMessage(boolean scrubForXssVulnerabilities) {
+		String msg = getMessage();
+		StringBuilder sb = new StringBuilder();
+		if (msg != null) {
+			if (scrubForXssVulnerabilities)
+				msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' ');
+			sb.append(msg);
+		}
+		Throwable e = getCause();
+		while (e != null) {
+			msg = e.getMessage();
+			if (msg != null && scrubForXssVulnerabilities)
+				msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' ');
+			String cls = e.getClass().getSimpleName();
+			if (msg == null)
+				sb.append(format("\nCaused by ({0})", cls));
+			else
+				sb.append(format("\nCaused by ({0}): {1}", cls, msg));
+			e = e.getCause();
+		}
+		return sb.toString();
+	}
+
+	@Override /* Object */
+	public int hashCode() {
+		int i = 0;
+		Throwable t = this;
+		while (t != null) {
+			for (StackTraceElement e : t.getStackTrace())
+			i ^= e.hashCode();
+			t = t.getCause();
+		}
+		return i;
+	}
+
+	void setOccurrence(int occurrence) {
+		this.occurrence = occurrence;
+	}
+
+	/**
+	 * Returns the number of times this exception occurred on this servlet.
+	 *
+	 * <p>
+	 * This only gets set if {@link RestContext#REST_useStackTraceHashes} is enabled on the servlet.
+	 *
+	 * @return
+	 * 	The occurrence number if {@link RestContext#REST_useStackTraceHashes} is enabled, or <code>0</code> otherwise.
+	 */
+	public int getOccurrence() {
+		return occurrence;
+	}
+
+	/**
+	 * Returns the HTTP status code.
+	 *
+	 * @return The HTTP status code.
+	 */
+	public int getStatus() {
+		return status;
+	}
+}

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