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/03/17 02:17:08 UTC

[3/7] incubator-juneau git commit: Allow @RestResource/@RestMethod annotations to be used on any classes.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
new file mode 100644
index 0000000..2b55b47
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestLogger.java
@@ -0,0 +1,233 @@
+// ***************************************************************************************************************************
+// * 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.text.*;
+import java.util.logging.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.annotation.*;
+
+/**
+ * Logging utility class.
+ * <p>
+ * Subclasses can override these methods to tailor logging of HTTP requests.
+ * Subclasses MUST implement a no-arg public constructor.
+ * <p>
+ * RestLoggers are associated with servlets/resources in one of the following ways:
+ * <ul>
+ * 	<li>The {@link RestResource#logger @RestResource.logger()} annotation.
+ * 	<li>The {@link RestConfig#setLogger(Class)}/{@link RestConfig#setLogger(RestLogger)} methods.
+ * </ul>
+ */
+public abstract class RestLogger {
+
+	/**
+	 * Returns the Java logger used for logging.
+	 * <p>
+	 * Subclasses can provide their own logger.
+	 * The default implementation returns the logger created using <code>Logger.getLogger(getClass())</code>.
+	 *
+	 * @return The logger used for logging.
+	 */
+	protected abstract Logger getLogger();
+
+	/**
+	 * Log a message to the logger.
+	 * <p>
+	 * Subclasses can override this method if they wish to log messages using a library other than
+	 * 	Java Logging (e.g. Apache Commons Logging).
+	 *
+	 * @param level The log level.
+	 * @param cause The cause.
+	 * @param msg The message to log.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 */
+	protected abstract void log(Level level, Throwable cause, String msg, Object...args);
+
+	/**
+	 * Log a message.
+	 * <p>
+	 * Equivalent to calling <code>log(level, <jk>null</jk>, msg, args);</code>
+	 *
+	 * @param level The log level.
+	 * @param msg The message to log.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 */
+	protected void log(Level level, String msg, Object...args) {
+		log(level, null, msg, args);
+	}
+
+	/**
+	 * Same as {@link #log(Level, String, Object...)} excepts runs the arguments through {@link JsonSerializer#DEFAULT_LAX_READABLE}.
+	 * <p>
+	 * Serialization of arguments do not occur if message is not logged, so it's safe to use this method from within debug log statements.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode'>
+	 * 	logObjects(<jsf>DEBUG</jsf>, <js>"Pojo contents:\n{0}"</js>, myPojo);
+	 * </p>
+	 *
+	 * @param level The log level.
+	 * @param msg The message to log.
+	 * @param args Optional {@link MessageFormat}-style arguments.
+	 */
+	protected void logObjects(Level level, String msg, Object...args) {
+		for (int i = 0; i < args.length; i++)
+			args[i] = JsonSerializer.DEFAULT_LAX_READABLE.toStringObject(args[i]);
+		log(level, null, msg, args);
+	}
+
+	/**
+	 * Callback method for logging errors during HTTP requests.
+	 * <p>
+	 * Typically, subclasses will override this method and log errors themselves.
+	 * <p>
+	 * The default implementation simply logs errors to the <code>RestServlet</code> logger.
+	 * <p>
+	 * Here's a typical implementation showing how stack trace hashing can be used to reduce log file sizes...
+	 * </p>
+	 * <p class='bcode'>
+	 * 	<jk>protected void</jk> onError(HttpServletRequest req, HttpServletResponse res, RestException e, <jk>boolean</jk> noTrace) {
+	 * 		String qs = req.getQueryString();
+	 * 		String msg = <js>"HTTP "</js> + req.getMethod() + <js>" "</js> + e.getStatus() + <js>" "</js> + req.getRequestURI() + (qs == <jk>null</jk> ? <js>""</js> : <js>"?"</js> + qs);
+	 * 		<jk>int</jk> c = e.getOccurrence();
+	 *
+	 * 		<jc>// REST_useStackTraceHashes is disabled, so we have to log the exception every time.</jc>
+	 * 		<jk>if</jk> (c == 0)
+	 * 			myLogger.log(Level.<jsf>WARNING</jsf>, <jsm>format</jsm>(<js>"[%s] %s"</js>, e.getStatus(), msg), e);
+	 *
+	 * 		<jc>// This is the first time we've countered this error, so log a stack trace
+	 * 		// unless ?noTrace was passed in as a URL parameter.</jc>
+	 * 		<jk>else if</jk> (c == 1 &amp;&amp; ! noTrace)
+	 * 			myLogger.log(Level.<jsf>WARNING</jsf>, <jsm>format</jsm>(<js>"[%h.%s.%s] %s"</js>, e.hashCode(), e.getStatus(), c, msg), e);
+	 *
+	 * 		<jc>// This error occurred before.
+	 * 		// Only log the message, not the stack trace.</jc>
+	 * 		<jk>else</jk>
+	 * 			myLogger.log(Level.<jsf>WARNING</jsf>, <jsm>format</jsm>(<js>"[%h.%s.%s] %s, %s"</js>, e.hashCode(), e.getStatus(), c, msg, e.getLocalizedMessage()));
+	 * 	}
+	 * </p>
+	 *
+	 * @param req The servlet request object.
+	 * @param res The servlet response object.
+	 * @param e Exception indicating what error occurred.
+	 */
+	protected void onError(HttpServletRequest req, HttpServletResponse res, RestException e) {
+		if (shouldLog(req, res, e)) {
+			String qs = req.getQueryString();
+			String msg = "HTTP " + req.getMethod() + " " + e.getStatus() + " " + req.getRequestURI() + (qs == null ? "" : "?" + qs);
+			int c = e.getOccurrence();
+			if (shouldLogStackTrace(req, res, e)) {
+				msg = '[' + Integer.toHexString(e.hashCode()) + '.' + e.getStatus() + '.' + c + "] " + msg;
+				log(Level.WARNING, e, msg);
+			} else {
+				msg = '[' + Integer.toHexString(e.hashCode()) + '.' + e.getStatus() + '.' + c + "] " + msg + ", " + e.getLocalizedMessage();
+				log(Level.WARNING, msg);
+			}
+		}
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified exception should be logged.
+	 * <p>
+	 * Subclasses can override this method to provide their own logic for determining when exceptions are logged.
+	 * <p>
+	 * The default implementation will return <jk>false</jk> if <js>"noTrace=true"</js> is passed in the query string.
+	 *
+	 * @param req The HTTP request.
+	 * @param res The HTTP response.
+	 * @param e The exception.
+	 * @return <jk>true</jk> if exception should be logged.
+	 */
+	protected boolean shouldLog(HttpServletRequest req, HttpServletResponse res, RestException e) {
+		String q = req.getQueryString();
+		return (q == null ? true : q.indexOf("noTrace=true") == -1);
+	}
+
+	/**
+	 * Returns <jk>true</jk> if a stack trace should be logged for this exception.
+	 * <p>
+	 * Subclasses can override this method to provide their own logic for determining when stack traces are logged.
+	 * <p>
+	 * The default implementation will only log a stack trace if {@link RestException#getOccurrence()} returns <code>1</code>
+	 * 	and the exception is not one of the following:
+	 * </p>
+	 * <ul>
+	 * 	<li>{@link HttpServletResponse#SC_UNAUTHORIZED}
+	 * 	<li>{@link HttpServletResponse#SC_FORBIDDEN}
+	 * 	<li>{@link HttpServletResponse#SC_NOT_FOUND}
+	 * </ul>
+	 *
+	 * @param req The HTTP request.
+	 * @param res The HTTP response.
+	 * @param e The exception.
+	 * @return <jk>true</jk> if stack trace should be logged.
+	 */
+	protected boolean shouldLogStackTrace(HttpServletRequest req, HttpServletResponse res, RestException e) {
+		if (e.getOccurrence() == 1) {
+			switch (e.getStatus()) {
+				case SC_UNAUTHORIZED:
+				case SC_FORBIDDEN:
+				case SC_NOT_FOUND:  return false;
+				default:            return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * NO-OP logger.
+	 * <p>
+	 * Disables all logging.
+	 *
+	 * @author James Bognar (james.bognar@salesforce.com)
+	 */
+	public static class NoOp extends RestLogger {
+
+		@Override /* RestLogger */
+		protected Logger getLogger() {
+			return null;
+		}
+
+		@Override /* RestLogger */
+		protected void log(Level level, Throwable cause, String msg, Object...args) {}
+	}
+
+	/**
+	 * Default logger.
+	 * <p>
+	 * Logs all messages to the logger returned by <code>Logger.<jsm>getLogger</jsm>(getClass().getName())</code>
+	 */
+	public static class Normal extends RestLogger {
+
+		private final JuneauLogger logger = JuneauLogger.getLogger(getClass());
+
+		@Override /* RestLogger */
+		protected Logger getLogger() {
+			return logger;
+		}
+
+		@Override /* RestLogger */
+		protected void log(Level level, Throwable cause, String msg, Object...args) {
+			if (args.length > 0)
+				msg = MessageFormat.format(msg, args);
+			getLogger().log(level, msg, cause);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java
index 4a2619d..2a1cc16 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java
@@ -15,7 +15,7 @@ package org.apache.juneau.rest;
 import java.lang.reflect.*;
 
 /**
- * Subclass of {@link RestMatcher} that gives access to the servlet and Java method it's applied to.
+ * Subclass of {@link RestMatcher} that gives access to the servlet/resource and Java method it's applied to.
  * <p>
  * Essentially the same as {@link RestMatcher} except has a constructor where the
  * 	Java method is passed in so that you can access annotations defined on it to tailor
@@ -26,8 +26,8 @@ public abstract class RestMatcherReflecting extends RestMatcher {
 	/**
 	 * Constructor.
 	 *
-	 * @param servlet The REST servlet.
+	 * @param resource The REST servlet.
 	 * @param javaMethod The Java method that this rest matcher is defined on.
 	 */
-	protected RestMatcherReflecting(RestServlet servlet, Method javaMethod) {}
+	protected RestMatcherReflecting(Object resource, Method javaMethod) {}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
index cc60feb..98aba93 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -19,6 +19,7 @@ import static javax.servlet.http.HttpServletResponse.*;
 import java.io.*;
 import java.lang.reflect.*;
 import java.net.*;
+import java.nio.charset.*;
 import java.text.*;
 import java.util.*;
 import java.util.logging.*;
@@ -63,13 +64,15 @@ import org.apache.juneau.utils.*;
 @SuppressWarnings("unchecked")
 public final class RestRequest extends HttpServletRequestWrapper {
 
-	private final RestServlet servlet;
+	private final RestContext context;
+
 	private final String method;
 	private String pathRemainder, body;
 	private Method javaMethod;
 	private ObjectMap properties;
 	private SerializerGroup serializerGroup;
 	private ParserGroup parserGroup;
+	private EncoderGroup encoders;
 	private Encoder encoder;
 	private int contentLength;
 	private final boolean debug;
@@ -89,17 +92,17 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	/**
 	 * Constructor.
 	 */
-	RestRequest(RestServlet servlet, HttpServletRequest req) throws ServletException {
+	RestRequest(RestContext context, HttpServletRequest req) throws ServletException {
 		super(req);
+		this.context = context;
 
 		try {
-			this.servlet = servlet;
 			isPost = req.getMethod().equalsIgnoreCase("POST");
 
 			// If this is a POST, we want to parse the query parameters ourselves to prevent
 			// the servlet code from processing the HTTP body as URL-Encoded parameters.
 			if (isPost)
-				queryParams = servlet.getUrlEncodingParser().parseIntoSimpleMap(getQueryString());
+				queryParams = context.getUrlEncodingParser().parseIntoSimpleMap(getQueryString());
 			else {
 				queryParams = req.getParameterMap();
 			}
@@ -109,23 +112,23 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			String _method = super.getMethod();
 
 			String m = getQueryParameter("method");
-			if (! StringUtils.isEmpty(m) && (servlet.context.allowMethodParams.contains(m) || servlet.context.allowMethodParams.contains("*")))
+			if (context.allowMethodParam(m))
 				_method = m;
 
 			method = _method;
 
-			if (servlet.context.allowBodyParam) {
+			if (context.isAllowBodyParam()) {
 				body = getQueryParameter("body");
 				if (body != null)
 					setHeader("Content-Type", UonSerializer.DEFAULT.getResponseContentType());
 			}
 
-			defaultServletHeaders = servlet.getDefaultRequestHeaders();
+			defaultServletHeaders = context.getDefaultRequestHeaders();
 
 			debug = "true".equals(getQueryParameter("debug", "false"));
 
 			if (debug) {
-				servlet.log(Level.INFO, toString());
+				context.getLogger().log(Level.INFO, toString());
 			}
 
 		} catch (RestException e) {
@@ -139,7 +142,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * Called from RestServlet after a match has been made but before the guard or method invocation.
 	 */
 	@SuppressWarnings("hiding")
-	final void init(Method javaMethod, String pathRemainder, ObjectMap properties, Map<String,String> mDefaultRequestHeaders, String defaultCharset, SerializerGroup mSerializers, ParserGroup mParsers, UrlEncodingParser mUrlEncodingParser) {
+	final void init(Method javaMethod, String pathRemainder, ObjectMap properties, Map<String,String> mDefaultRequestHeaders, String defaultCharset, SerializerGroup mSerializers, ParserGroup mParsers, UrlEncodingParser mUrlEncodingParser, EncoderGroup encoders) {
 		this.javaMethod = javaMethod;
 		this.pathRemainder = pathRemainder;
 		this.properties = properties;
@@ -149,6 +152,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 		this.urlEncodingParser = mUrlEncodingParser;
 		this.beanSession = urlEncodingParser.getBeanContext().createSession();
 		this.defaultCharset = defaultCharset;
+		this.encoders = encoders;
 	}
 
 	/**
@@ -403,7 +407,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			}
 			if (charset == null)
 				charset = defaultCharset;
-			if (! RestServlet.availableCharsets.containsKey(charset))
+			if (! Charset.isSupported(charset))
 				throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported charset in header ''Content-Type'': ''{0}''", h);
 		}
 		return charset;
@@ -1299,11 +1303,11 @@ public final class RestRequest extends HttpServletRequestWrapper {
 					properties.append("mediaType", mediaType).append("characterEncoding", getCharacterEncoding());
 					if (! p.isReaderParser()) {
 						InputStreamParser p2 = (InputStreamParser)p;
-						ParserSession session = p2.createSession(getInputStream(), properties, getJavaMethod(), getServlet(), locale, timeZone, mediaType);
+						ParserSession session = p2.createSession(getInputStream(), properties, getJavaMethod(), context.getResource(), locale, timeZone, mediaType);
 						return p2.parseSession(session, cm);
 					}
 					ReaderParser p2 = (ReaderParser)p;
-					ParserSession session = p2.createSession(getUnbufferedReader(), properties, getJavaMethod(), getServlet(), locale, timeZone, mediaType);
+					ParserSession session = p2.createSession(getUnbufferedReader(), properties, getJavaMethod(), context.getResource(), locale, timeZone, mediaType);
 					return p2.parseSession(session, cm);
 				} catch (ParseException e) {
 					throw new RestException(SC_BAD_REQUEST,
@@ -1576,45 +1580,45 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	/**
 	 * Returns the localized servlet title.
 	 * <p>
-	 * Equivalent to calling {@link RestServlet#getTitle(RestRequest)} with this object.
+	 * Equivalent to calling {@link RestInfoProvider#getTitle(RestRequest)} with this object.
 	 *
 	 * @return The localized servlet label.
 	 */
 	public String getServletTitle() {
-		return servlet.getTitle(this);
+		return context.getInfoProvider().getTitle(this);
 	}
 
 	/**
 	 * Returns the localized servlet description.
 	 * <p>
-	 * Equivalent to calling {@link RestServlet#getDescription(RestRequest)} with this object.
+	 * Equivalent to calling {@link RestInfoProvider#getDescription(RestRequest)} with this object.
 	 *
 	 * @return The localized servlet description.
 	 */
 	public String getServletDescription() {
-		return servlet.getDescription(this);
+		return context.getInfoProvider().getDescription(this);
 	}
 
 	/**
 	 * Returns the localized method summary.
 	 * <p>
-	 * Equivalent to calling {@link RestServlet#getMethodSummary(String, RestRequest)} with this object.
+	 * Equivalent to calling {@link RestInfoProvider#getMethodSummary(String, RestRequest)} with this object.
 	 *
 	 * @return The localized method description.
 	 */
 	public String getMethodSummary() {
-		return servlet.getMethodSummary(javaMethod.getName(), this);
+		return context.getInfoProvider().getMethodSummary(javaMethod.getName(), this);
 	}
 
 	/**
 	 * Returns the localized method description.
 	 * <p>
-	 * Equivalent to calling {@link RestServlet#getMethodDescription(String, RestRequest)} with this object.
+	 * Equivalent to calling {@link RestInfoProvider#getMethodDescription(String, RestRequest)} with this object.
 	 *
 	 * @return The localized method description.
 	 */
 	public String getMethodDescription() {
-		return servlet.getMethodDescription(javaMethod.getName(), this);
+		return context.getInfoProvider().getMethodDescription(javaMethod.getName(), this);
 	}
 
 
@@ -1713,14 +1717,14 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * Shortcut method for calling {@link RestServlet#getMessage(Locale, String, Object...)} based on the request locale.
+	 * Shortcut method for calling {@link MessageBundle#getString(Locale, String, Object...)} based on the request locale.
 	 *
 	 * @param key The message key.
 	 * @param args Optional {@link MessageFormat}-style arguments.
 	 * @return The localized message.
 	 */
 	public String getMessage(String key, Object...args) {
-		return servlet.getMessage(getLocale(), key, args);
+		return context.getMessages().getString(getLocale(), key, args);
 	}
 
 	/**
@@ -1729,7 +1733,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @return The resource bundle.  Never <jk>null</jk>.
 	 */
 	public MessageBundle getResourceBundle() {
-		return servlet.getMessages(getLocale());
+		return context.getMessages().getBundle(getLocale());
 	}
 
 	/**
@@ -1740,8 +1744,8 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 *
 	 * @return The servlet handling the request.
 	 */
-	public RestServlet getServlet() {
-		return servlet;
+	public RestContext getContext() {
+		return context;
 	}
 
 	/**
@@ -1772,13 +1776,13 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * Returns the variable resolver session for this request using session objects created by {@link RestServlet#getSessionObjects(RestRequest)}.
+	 * Returns the variable resolver session for this request using session objects created by {@link RestCallHandler#getSessionObjects(RestRequest)}.
 	 *
 	 * @return The variable resolver for this request.
 	 */
 	public VarResolverSession getVarResolverSession() {
 		if (varSession == null)
-			varSession = servlet.getVarResolver().createSession(servlet.getSessionObjects(this));
+			varSession = context.getVarResolver().createSession(context.getCallHandler().getSessionObjects(this));
 		return varSession;
 	}
 
@@ -1803,7 +1807,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @throws IOException
 	 */
 	public ReaderResource getReaderResource(String name, boolean resolveVars, MediaType mediaType) throws IOException {
-		String s = servlet.getResourceAsString(name, getLocale());
+		String s = context.getResourceAsString(name, getLocale());
 		if (s == null)
 			return null;
 		ReaderResource.Builder b = new ReaderResource.Builder().mediaType(mediaType).contents(s);
@@ -1813,8 +1817,8 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	}
 
 	/**
-	 * Same as {@link #getReaderResource(String, boolean, MediaType)} except uses {@link RestServlet#getMimetypesFileTypeMap()}
-	 * to determine the media type.
+	 * Same as {@link #getReaderResource(String, boolean, MediaType)} except uses the resource mime-type map
+	 * constructed using {@link RestConfig#addMimeTypes(String...)} to determine the media type.
 	 *
 	 * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}.
 	 * @param resolveVars If <jk>true</jk>, any {@link org.apache.juneau.rest.annotation.Parameter} variables will be resolved by the variable resolver returned
@@ -1823,7 +1827,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @throws IOException
 	 */
 	public ReaderResource getReaderResource(String name, boolean resolveVars) throws IOException {
-		return getReaderResource(name, resolveVars, MediaType.forString(servlet.getMimetypesFileTypeMap().getContentType(name)));
+		return getReaderResource(name, resolveVars, MediaType.forString(context.getMediaTypeForName(name)));
 	}
 
 	/**
@@ -1834,7 +1838,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 * @throws IOException
 	 */
 	public ReaderResource getReaderResource(String name) throws IOException {
-		return getReaderResource(name, false, MediaType.forString(servlet.getMimetypesFileTypeMap().getContentType(name)));
+		return getReaderResource(name, false, MediaType.forString(context.getMediaTypeForName(name)));
 	}
 
 	/**
@@ -1842,9 +1846,9 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 *
 	 * @return The config file associated with the servlet, or <jk>null</jk> if servlet does not have a config file associated with it.
 	 */
-	public ConfigFile getConfig() {
+	public ConfigFile getConfigFile() {
 		if (cf == null)
-			cf = servlet.getConfig().getResolving(getVarResolverSession());
+			cf = context.getConfigFile().getResolving(getVarResolverSession());
 		return cf;
 	}
 
@@ -1855,7 +1859,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 */
 	public Swagger getSwagger() {
 		if (swagger == null)
-			swagger = servlet.getSwagger(this);
+			swagger = context.getInfoProvider().getSwagger(this);
 		return swagger;
 	}
 
@@ -1871,7 +1875,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 */
 	protected Swagger getSwaggerFromFile() {
 		if (fileSwagger == null)
-			fileSwagger = servlet.getSwaggerFromFile(this.getLocale());
+			fileSwagger = context.getInfoProvider().getSwaggerFromFile(this.getLocale());
 		if (fileSwagger == null)
 			fileSwagger = Swagger.NULL;
 		return fileSwagger == Swagger.NULL ? null : fileSwagger;
@@ -1895,7 +1899,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 				sb.append(getBodyAsString()).append("\n");
 			} catch (Exception e1) {
 				sb.append(e1.getLocalizedMessage());
-				servlet.log(WARNING, e1, "Error occurred while trying to read debug input.");
+				context.getLogger().log(WARNING, e1, "Error occurred while trying to read debug input.");
 			}
 		}
 		return sb.toString();
@@ -1942,11 +1946,11 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			String ce = getHeader("content-encoding");
 			if (! (ce == null || ce.isEmpty())) {
 				ce = ce.trim();
-				encoder = servlet.getEncoders().getEncoder(ce);
+				encoder = encoders.getEncoder(ce);
 				if (encoder == null)
 					throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE,
 						"Unsupported encoding in request header ''Content-Encoding'': ''{0}''\n\tSupported codings: {1}",
-						getHeader("content-encoding"), servlet.getEncoders().getSupportedEncodings()
+						getHeader("content-encoding"), encoders.getSupportedEncodings()
 					);
 			}
 
@@ -1966,7 +1970,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 */
 	private String getOverriddenHeader(String name) {
 		String h = null;
-		if (servlet.context.allowHeaderParams)
+		if (context.isAllowHeaderParams())
 			h = getQueryParameter(name);
 		if (h != null)
 			return h;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestResourceResolver.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestResourceResolver.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestResourceResolver.java
new file mode 100644
index 0000000..7989eb9
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestResourceResolver.java
@@ -0,0 +1,80 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.annotation.*;
+
+/**
+ * Class used to resolve {@link Class} objects to instances.
+ * <p>
+ * Used to convert classes defined via {@link RestResource#children() @RestResource.children()} into child instances.
+ * <p>
+ * Subclasses can be created to provide customized resource resolution.
+ * These can be associated with REST resources in one of the following ways:
+ * <ul>
+ * 	<li>{@link RestResource#resourceResolver() @RestResource.resourceResolver()} annotation.
+ * 	<li>{@link RestConfig#setResourceResolver(Class)}/{@link RestConfig#setResourceResolver(RestResourceResolver)} methods.
+ * </ul>
+ * <p>
+ * The default implementation simply instantiates the class using one of the following constructors:
+ * <ul>
+ * 	<li><code><jk>public</jk> T(RestConfig)</code>
+ * 	<li><code><jk>public</jk> T()</code>
+ * </ul>
+ * The former constructor can be used to get access to the {@link RestConfig} object to get access to the
+ * config file and initialization information or make programmatic modifications to the resource before
+ * full initialization.
+ * <p>
+ * Non-<code>RestServlet</code> classes can also add the following two methods to get access to the
+ * {@link RestConfig} and {@link RestContext} objects:
+ * <ul>
+ * 	<li><jk>public void</jk> init(RestConfig);</code>
+ * 	<li><jk>public void</jk> init(RestContext);</code>
+ * </ul>
+ */
+public class RestResourceResolver {
+
+	/**
+	 * Denotes the default resolver.
+	 */
+	public static final class DEFAULT extends RestResourceResolver {}
+
+	/**
+	 * Resolves the specified class to a resource object.
+	 * <p>
+	 * Subclasses can override this method to provide their own custom resolution.
+	 * <p>
+	 * The default implementation simply creates a new class instance using {@link Class#newInstance()}.
+	 *
+	 * @param c The class to resolve.
+	 * @param config The initialization configuration for the resource.
+	 * @return The instance of that class.
+	 * @throws RestServletException If class could not be resolved.
+	 */
+	public Object resolve(Class<?> c, RestConfig config) throws RestServletException {
+		try {
+			Constructor<?> c1 = ClassUtils.findPublicConstructor(c, RestConfig.class);
+			if (c1 != null)
+				return c1.newInstance(config);
+			c1 = ClassUtils.findPublicConstructor(c);
+			if (c1 != null)
+				return c1.newInstance();
+		} catch (Exception e) {
+			throw new RestServletException("Could not instantiate resource class ''{0}''", c.getName()).initCause(e);
+		}
+		throw new RestServletException("Could not find public constructor for class ''{0}''.", c);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/07843d64/juneau-rest/src/main/java/org/apache/juneau/rest/RestResponse.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestResponse.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestResponse.java
index 3fa5398..637dc7e 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.rest;
 
 import java.io.*;
+import java.nio.charset.*;
 import java.util.*;
 
 import javax.servlet.*;
@@ -55,24 +56,22 @@ public final class RestResponse extends HttpServletResponseWrapper {
 	SerializerGroup serializerGroup;
 	UrlEncodingSerializer urlEncodingSerializer;         // The serializer used to convert arguments passed into Redirect objects.
 	private EncoderGroup encoders;
-	private final RestServlet servlet;
 	private ServletOutputStream os;
 
 	/**
 	 * Constructor.
 	 */
-	RestResponse(RestServlet servlet, RestRequest req, HttpServletResponse res) {
+	RestResponse(RestContext context, RestRequest req, HttpServletResponse res) {
 		super(res);
 		this.request = req;
-		this.servlet = servlet;
 
-		for (Map.Entry<String,Object> e : servlet.getDefaultResponseHeaders().entrySet())
+		for (Map.Entry<String,Object> e : context.getDefaultResponseHeaders().entrySet())
 			setHeader(e.getKey(), e.getValue().toString());
 
 		try {
 			String passThroughHeaders = req.getHeader("x-response-headers");
 			if (passThroughHeaders != null) {
-				ObjectMap m = servlet.getUrlEncodingParser().parseParameter(passThroughHeaders, ObjectMap.class);
+				ObjectMap m = context.getUrlEncodingParser().parseParameter(passThroughHeaders, ObjectMap.class);
 				for (Map.Entry<String,Object> e : m.entrySet())
 					setHeader(e.getKey(), e.getValue().toString());
 			}
@@ -101,7 +100,7 @@ public final class RestResponse extends HttpServletResponseWrapper {
 				MediaType mt = r.getMediaType();
 				if (mt.getType().equals("*"))
 					charset = defaultCharset;
-				else if (RestServlet.availableCharsets.containsKey(mt.getType()))
+				else if (Charset.isSupported(mt.getType()))
 					charset = mt.getType();
 				if (charset != null)
 					break;
@@ -138,7 +137,7 @@ public final class RestResponse extends HttpServletResponseWrapper {
 	 * @throws RestServletException
 	 */
 	public List<String> getSupportedEncodings() throws RestServletException {
-		return servlet.getEncoders().getSupportedEncodings();
+		return encoders.getSupportedEncodings();
 	}
 
 	/**