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 2020/03/18 19:49:17 UTC

[juneau] branch master updated: JUNEAU-198 & JUNEAU-189

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 304d99c  JUNEAU-198 & JUNEAU-189
304d99c is described below

commit 304d99cd252172cdd838ec1962b7db2402245a2e
Author: JamesBognar <ja...@apache.org>
AuthorDate: Wed Mar 18 15:48:58 2020 -0400

    JUNEAU-198 & JUNEAU-189
    
    Need improved ability to turn on debug in REST via environment
    variables.
    @ReestMethod(debug="true") does not appear to cause HTTP requests to be
    logged.
---
 .../apache/juneau/reflection/ClassInfoTest.java    |   62 +-
 .../juneau/reflection/ExecutableInfoTest.java      |   12 +-
 .../org/apache/juneau/internal/ClassUtils.java     |  111 +-
 .../java/org/apache/juneau/reflect/ClassInfo.java  |  125 +-
 .../org/apache/juneau/reflect/ExecutableInfo.java  |   93 +-
 .../org/apache/juneau/transform/AutoListSwap.java  |    2 +-
 .../org/apache/juneau/transform/AutoMapSwap.java   |    2 +-
 .../apache/juneau/transform/AutoNumberSwap.java    |    2 +-
 .../apache/juneau/transform/AutoObjectSwap.java    |    2 +-
 .../org/apache/juneau/utils/ReflectionMap.java     |   96 +-
 juneau-doc/docs/ReleaseNotes/8.1.4.html            |  138 ++-
 .../juneau/examples/rest/HelloWorldResource.java   |   25 -
 .../juneau/rest/mock2/MockServletRequest.java      |    3 +-
 .../java/org/apache/juneau/rest/DebugModeTest.java | 1195 ++++++++++++++++++++
 .../java/org/apache/juneau/rest/BasicRest.java     |  415 ++++++-
 .../apache/juneau/rest/BasicRestCallHandler.java   |   14 +-
 .../apache/juneau/rest/BasicRestCallLogger.java    |   25 +-
 .../org/apache/juneau/rest/BasicRestConfig.java    |   96 +-
 ...{BasicRestConfig.java => BasicRestMethods.java} |  123 +-
 .../org/apache/juneau/rest/BasicRestServlet.java   |   14 +-
 .../java/org/apache/juneau/rest/Enablement.java    |   13 +
 .../{Enablement.java => NoOpRestCallLogger.java}   |   38 +-
 .../main/java/org/apache/juneau/rest/RestCall.java |   24 +-
 .../apache/juneau/rest/RestCallLoggerConfig.java   |    2 +-
 .../java/org/apache/juneau/rest/RestContext.java   |  146 ++-
 .../org/apache/juneau/rest/RestContextBuilder.java |   27 +-
 .../org/apache/juneau/rest/RestMethodContext.java  |  106 +-
 .../java/org/apache/juneau/rest/RestRequest.java   |   13 +-
 .../java/org/apache/juneau/rest/StatusStats.java   |    1 +
 .../org/apache/juneau/rest/annotation/Rest.java    |  195 +++-
 .../juneau/rest/annotation/RestConfigApply.java    |    8 +-
 .../apache/juneau/rest/annotation/RestMethod.java  |   11 +-
 .../rest/annotation/RestMethodConfigApply.java     |    6 +-
 33 files changed, 2614 insertions(+), 531 deletions(-)

diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ClassInfoTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ClassInfoTest.java
index 1f60dca..56a822c 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ClassInfoTest.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ClassInfoTest.java
@@ -699,7 +699,7 @@ public class ClassInfoTest {
 		check("E1(int)", e1.getConstructor(Visibility.PRIVATE, int.class));
 		check(null, e1.getConstructor(Visibility.PUBLIC, int.class));
 		check("E3()", e3.getConstructor(Visibility.PUBLIC));
-		check("E4(ClassInfoTest)", e4.getConstructor(Visibility.PUBLIC));
+		check(null, e4.getConstructor(Visibility.PUBLIC));
 		check("E5()", e5.getConstructor(Visibility.PUBLIC));
 	}
 
@@ -2259,6 +2259,66 @@ public class ClassInfoTest {
 		check("MM", mn.getParameterType(1, HashMap.class));
 	}
 
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// ClassInfo.getStaticCreator(Object...)
+	// ClassInfo.getStaticCreatorFuzzy(Object...)
+	//-----------------------------------------------------------------------------------------------------------------
+
+	public static class N1 {}
+	public static ClassInfo n1 = ClassInfo.of(N1.class);
+
+	@Test
+	public void n01_noCreators() {
+		assertNull(n1.getStaticCreator());
+		assertNull(n1.getStaticCreatorFuzzy());
+	}
+
+	public static class N2 {
+		public static N2 create() {return null;}
+	}
+	public static ClassInfo n2 = ClassInfo.of(N2.class);
+
+	@Test
+	public void n02_noArgCreator() {
+		assertNotNull(n2.getStaticCreator());
+		assertNotNull(n2.getStaticCreatorFuzzy());
+	}
+
+	public static class N3 {
+		public static N3 create(String foo, int bar) {return null;}
+	}
+	public static ClassInfo n3 = ClassInfo.of(N3.class);
+
+	@Test
+	public void n03_withArgCreators() {
+		assertNull(n3.getStaticCreator());
+		assertNull(n3.getStaticCreatorFuzzy());
+		assertNotNull(n3.getStaticCreator("foo", 123));
+		assertNotNull(n3.getStaticCreatorFuzzy("foo", 123));
+		assertNotNull(n3.getStaticCreator(123, "foo"));
+		assertNotNull(n3.getStaticCreatorFuzzy(123, "foo"));
+		assertNull(n3.getStaticCreator("foo", 123, new File(".")));
+		assertNotNull(n3.getStaticCreatorFuzzy("foo", 123, new File(".")));
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// ClassInfo.isParentOfFuzzyPrimitives(Class)
+	// ClassInfo.isParentOfFuzzyPrimitives(Type)
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@Test
+	public void o01_isParentOfFuzzyPrimitives() {
+		assertTrue(ClassInfo.of(String.class).isParentOfFuzzyPrimitives(String.class));
+		assertTrue(ClassInfo.of(CharSequence.class).isParentOfFuzzyPrimitives(String.class));
+		assertFalse(ClassInfo.of(String.class).isParentOfFuzzyPrimitives(CharSequence.class));
+		assertTrue(ClassInfo.of(int.class).isParentOfFuzzyPrimitives(Integer.class));
+		assertTrue(ClassInfo.of(Integer.class).isParentOfFuzzyPrimitives(int.class));
+		assertTrue(ClassInfo.of(Number.class).isParentOfFuzzyPrimitives(int.class));
+		assertFalse(ClassInfo.of(int.class).isParentOfFuzzyPrimitives(Number.class));
+		assertFalse(ClassInfo.of(int.class).isParentOfFuzzyPrimitives(long.class));
+	}
+
 	//-----------------------------------------------------------------------------------------------------------------
 	// Other
 	//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java
index d3b7ff8..18756e2 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/reflection/ExecutableInfoTest.java
@@ -483,12 +483,12 @@ public class ExecutableInfoTest {
 
 	@Test
 	public void hasArgParents() {
-		assertTrue(e_hasStringParam.hasParamTypeParents(String.class));
-		assertTrue(e_hasStringParam.hasParamTypeParents(CharSequence.class));
-		assertFalse(e_hasStringParam.hasParamTypeParents(StringBuilder.class));
-		assertFalse(e_hasStringParam.hasParamTypeParents(new Class[0]));
-		assertFalse(e_hasStringParam.hasParamTypeParents(String.class, String.class));
-		assertFalse(e_hasStringParam.hasParamTypeParents(long.class));
+		assertTrue(e_hasStringParam.hasMatchingParamTypes(String.class));
+		assertFalse(e_hasStringParam.hasMatchingParamTypes(CharSequence.class));
+		assertFalse(e_hasStringParam.hasMatchingParamTypes(StringBuilder.class));
+		assertFalse(e_hasStringParam.hasMatchingParamTypes(new Class[0]));
+		assertFalse(e_hasStringParam.hasMatchingParamTypes(String.class, String.class));
+		assertFalse(e_hasStringParam.hasMatchingParamTypes(long.class));
 	}
 
 	@Test
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java
index 2a9f123..d125673 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ClassUtils.java
@@ -38,108 +38,6 @@ public final class ClassUtils {
 	}
 
 	/**
-	 * Returns <jk>true</jk> if the specified argument types are valid for the specified parameter types.
-	 *
-	 * @param paramTypes The parameters types specified on a method.
-	 * @param argTypes The class types of the arguments being passed to the method.
-	 * @return <jk>true</jk> if the arguments match the parameters.
-	 */
-	public static boolean argsMatch(Class<?>[] paramTypes, Class<?>[] argTypes) {
-		if (paramTypes.length == argTypes.length) {
-			for (int i = 0; i < paramTypes.length; i++)
-				if (! ClassInfo.of(paramTypes[i]).isParentOf(argTypes[i]))
-					return false;
-			return true;
-		}
-		return false;
-	}
-
-	/**
-	 * Returns <jk>true</jk> if the specified argument types are valid for the specified parameter types.
-	 *
-	 * @param paramTypes The parameters types specified on a method.
-	 * @param argTypes The class types of the arguments being passed to the method.
-	 * @return <jk>true</jk> if the arguments match the parameters.
-	 */
-	public static boolean argsMatch(List<ClassInfo> paramTypes, Class<?>[] argTypes) {
-		if (paramTypes.size() == argTypes.length) {
-			for (int i = 0; i < paramTypes.size(); i++)
-				if (! paramTypes.get(i).isParentOf(argTypes[i]))
-					return false;
-			return true;
-		}
-		return false;
-	}
-
-	/**
-	 * Returns a number representing the number of arguments that match the specified parameters.
-	 *
-	 * @param paramTypes The parameters types specified on a method.
-	 * @param argTypes The class types of the arguments being passed to the method.
-	 * @return The number of matching arguments, or <c>-1</c> a parameter was found that isn't in the list of args.
-	 */
-	public static int fuzzyArgsMatch(Class<?>[] paramTypes, Class<?>... argTypes) {
-		int matches = 0;
-		outer: for (Class<?> p : paramTypes) {
-			ClassInfo pi = ClassInfo.of(p).getWrapperInfoIfPrimitive();
-			for (Class<?> a : argTypes) {
-				ClassInfo ai = ClassInfo.of(a).getWrapperInfoIfPrimitive();
-				if (pi.isParentOf(ai.inner())) {
-					matches++;
-					continue outer;
-				}
-			}
-			return -1;
-		}
-		return matches;
-	}
-
-	/**
-	 * Returns a number representing the number of arguments that match the specified parameters.
-	 *
-	 * @param paramTypes The parameters types specified on a method.
-	 * @param argTypes The class types of the arguments being passed to the method.
-	 * @return The number of matching arguments, or <c>-1</c> a parameter was found that isn't in the list of args.
-	 */
-	public static int fuzzyArgsMatch(Class<?>[] paramTypes, ClassInfo... argTypes) {
-		int matches = 0;
-		outer: for (Class<?> p : paramTypes) {
-			ClassInfo pi = ClassInfo.of(p).getWrapperInfoIfPrimitive();
-			for (ClassInfo a : argTypes) {
-				ClassInfo ai = a.getWrapperInfoIfPrimitive();
-				if (pi.isParentOf(ai.inner())) {
-					matches++;
-					continue outer;
-				}
-			}
-			return -1;
-		}
-		return matches;
-	}
-
-	/**
-	 * Returns a number representing the number of arguments that match the specified parameters.
-	 *
-	 * @param paramTypes The parameters types specified on a method.
-	 * @param argTypes The class types of the arguments being passed to the method.
-	 * @return The number of matching arguments, or <c>-1</c> a parameter was found that isn't in the list of args.
-	 */
-	public static int fuzzyArgsMatch(List<ClassInfo> paramTypes, Class<?>... argTypes) {
-		int matches = 0;
-		outer: for (ClassInfo p : paramTypes) {
-			p = p.getWrapperInfoIfPrimitive();
-			for (Class<?> a : argTypes) {
-				if (p.isParentOf(a)) {
-					matches++;
-					continue outer;
-				}
-			}
-			return -1;
-		}
-		return matches;
-	}
-
-	/**
 	 * Returns the class types for the specified arguments.
 	 *
 	 * @param args The objects we're getting the classes of.
@@ -221,6 +119,11 @@ public final class ClassUtils {
 		if (c2 instanceof Class) {
 			try {
 				ClassInfo c3 = ClassInfo.of((Class<?>)c2);
+
+				MethodInfo mi = c3.getStaticCreator(args);
+				if (mi != null)
+					return mi.invoke(null, args);
+
 				if (c3.isInterface() || c3.isAbstract())
 					return null;
 
@@ -239,6 +142,10 @@ public final class ClassUtils {
 
 				// Finally use fuzzy matching.
 				if (fuzzyArgs) {
+					mi = c3.getStaticCreatorFuzzy(args);
+					if (mi != null)
+						return mi.invoke(null, getMatchingArgs(mi.getParamTypes(), args));
+
 					con = c3.getPublicConstructorFuzzy(args);
 					if (con != null)
 						return con.<T>invoke(getMatchingArgs(con.getParamTypes(), args));
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
index d63aa1f..41ddb37 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ClassInfo.java
@@ -567,6 +567,66 @@ public final class ClassInfo {
 	}
 
 	/**
+	 * Find the public static creator method on this class.
+	 *
+	 * <p>
+	 * Must have the following signature where T is the exact outer class.
+	 * <p class='bcode w800'>
+	 * 	public static T create(...);
+	 * </p>
+	 *
+	 * <p>
+	 * Must be able to take in all arguments specified in any order.
+	 *
+	 * @param args The arguments to pass to the create method.
+	 * @return The static method, or <jk>null</jk> if it couldn't be found.
+	 */
+	public MethodInfo getStaticCreator(Object...args) {
+		if (c != null) {
+			Class<?>[] argTypes = ClassUtils.getClasses(args);
+			for (MethodInfo m : getPublicMethods()) {
+				if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && m.hasReturnType(c) && m.getSimpleName().equals("create") && m.hasMatchingParamTypes(argTypes))
+					return m;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Find the public static creator method on this class.
+	 *
+	 * <p>
+	 * Must have the following signature where T is the exact outer class.
+	 * <p class='bcode w800'>
+	 * 	public static T create(...);
+	 * </p>
+	 *
+	 * <p>
+	 * Returned method can take in arguments in any order if they match.  The method is guaranteed to not require additional
+	 * arguments not specified.
+	 *
+	 * @param args The arguments to pass to the create method.
+	 * @return The static method, or <jk>null</jk> if it couldn't be found.
+	 */
+	public MethodInfo getStaticCreatorFuzzy(Object...args) {
+		int bestCount = -1;
+		MethodInfo bestMatch = null;
+		if (c != null) {
+			Class<?>[] argTypes = ClassUtils.getClasses(args);
+			for (MethodInfo m : getPublicMethods()) {
+				if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && m.hasReturnType(c) && m.getSimpleName().equals("create")) {
+					int mn = m.fuzzyArgsMatch(argTypes);
+					if (mn > bestCount) {
+						bestCount = mn;
+						bestMatch = m;
+					}
+				}
+			}
+		}
+		return bestMatch;
+	}
+
+	/**
 	 * Find the public static method with the specified name and args.
 	 *
 	 * @param name The method name.
@@ -750,7 +810,7 @@ public final class ClassInfo {
 			ConstructorInfo bestMatch = null;
 			for (ConstructorInfo n : _getDeclaredConstructors()) {
 				if (vis.isVisible(n.inner())) {
-					int m = ClassUtils.fuzzyArgsMatch(n.getParamTypes(), argTypes);
+					int m = n.fuzzyArgsMatch(argTypes);
 					if (m > bestCount) {
 						bestCount = m;
 						bestMatch = n;
@@ -765,7 +825,7 @@ public final class ClassInfo {
 			List<ClassInfo> paramTypes = n.getParamTypes();
 			if (isMemberClass)
 				paramTypes = paramTypes.subList(1, paramTypes.size());
-			if (ClassUtils.argsMatch(paramTypes, argTypes) && vis.isVisible(n.inner()))
+			if (n.hasMatchingParamTypes(argTypes) && vis.isVisible(n.inner()))
 				return n;
 		}
 
@@ -1919,6 +1979,55 @@ public final class ClassInfo {
 	/**
 	 * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
 	 *
+	 * <p>
+	 * Primitive classes are converted to wrapper classes and compared.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode w800'>
+	 * 		ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).isParentOfFuzzyPrimitives(String.<jk>class</jk>);  <jc>// true</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(CharSequence.<jk>class</jk>).isParentOfFuzzyPrimitives(String.<jk>class</jk>);  <jc>// true</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).isParentOfFuzzyPrimitives(CharSequence.<jk>class</jk>);  <jc>// false</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfFuzzyPrimitives(Integer.<jk>class</jk>);  <jc>// true</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(Integer.<jk>class</jk>).isParentOfFuzzyPrimitives(<jk>int</jk>.<jk>class</jk>);  <jc>// true</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(Number.<jk>class</jk>).isParentOfFuzzyPrimitives(<jk>int</jk>.<jk>class</jk>);  <jc>// true</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfFuzzyPrimitives(Number.<jk>class</jk>);  <jc>// false</jc>
+	 * 		ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfFuzzyPrimitives(<jk>long</jk>.<jk>class</jk>);  <jc>// false</jc>
+	 * </p>
+	 *
+	 * @param child The child class.
+	 * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
+	 */
+	public boolean isParentOfFuzzyPrimitives(Class<?> child) {
+		if (c == null || child == null)
+			return false;
+		if (c.isAssignableFrom(child))
+			return true;
+		if (this.isPrimitive() || child.isPrimitive()) {
+			return this.getWrapperIfPrimitive().isAssignableFrom(of(child).getWrapperIfPrimitive());
+		}
+		return false;
+	}
+
+	/**
+	 * Same as {@link #isParentOfFuzzyPrimitives(Class)} but takes in a {@link ClassInfo}.
+	 *
+	 * @param child The child class.
+	 * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
+	 */
+	public boolean isParentOfFuzzyPrimitives(ClassInfo child) {
+		if (c == null || child == null)
+			return false;
+		if (c.isAssignableFrom(child.inner()))
+			return true;
+		if (this.isPrimitive() || child.isPrimitive()) {
+			return this.getWrapperIfPrimitive().isAssignableFrom(child.getWrapperIfPrimitive());
+		}
+		return false;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
+	 *
 	 * @param child The child class.
 	 * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
 	 */
@@ -1929,6 +2038,18 @@ public final class ClassInfo {
 	}
 
 	/**
+	 * Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
+	 *
+	 * @param child The child class.
+	 * @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
+	 */
+	public boolean isParentOfFuzzyPrimitives(Type child) {
+		if (child instanceof Class)
+			return isParentOfFuzzyPrimitives((Class<?>)child);
+		return false;
+	}
+
+	/**
 	 * Returns <jk>true</jk> if this class is a child of <c>parent</c>.
 	 *
 	 * @param parent The parent class.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
index 026d63c..a0a7963 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/ExecutableInfo.java
@@ -500,15 +500,19 @@ public abstract class ExecutableInfo {
 	 * @param args The arguments to test for.
 	 * @return <jk>true</jk> if this method has this arguments in the exact order.
 	 */
-	public final boolean hasParamTypeParents(Class<?>...args) {
-		Class<?>[] pt = _getRawParamTypes();
-		if (pt.length == args.length) {
-			for (int i = 0; i < pt.length; i++)
-				if (! args[i].isAssignableFrom(pt[i]))
-					return false;
-			return true;
+	public final boolean hasMatchingParamTypes(Class<?>...args) {
+		ClassInfo[] pt = _getParamTypes();
+		if (pt.length != args.length)
+			return false;
+		for (int i = 0; i < pt.length; i++) {
+			boolean matched = false;
+			for (int j = 0; j < args.length; j++)
+				if (pt[i].isParentOfFuzzyPrimitives(args[j]))
+					matched = true;
+			if (! matched)
+				return false;
 		}
-		return false;
+		return true;
 	}
 
 	/**
@@ -517,15 +521,19 @@ public abstract class ExecutableInfo {
 	 * @param args The arguments to test for.
 	 * @return <jk>true</jk> if this method has this arguments in the exact order.
 	 */
-	public final boolean hasParamTypeParents(ClassInfo...args) {
-		Class<?>[] pt = _getRawParamTypes();
-		if (pt.length == args.length) {
-			for (int i = 0; i < pt.length; i++)
-				if (! args[i].inner().isAssignableFrom(pt[i]))
-					return false;
-			return true;
+	public final boolean hasMatchingParamTypes(ClassInfo...args) {
+		ClassInfo[] pt = _getParamTypes();
+		if (pt.length != args.length)
+			return false;
+		for (int i = 0; i < pt.length; i++) {
+			boolean matched = false;
+			for (int j = 0; j < args.length; j++)
+				if (pt[i].isParentOfFuzzyPrimitives(args[j].inner()))
+					matched = true;
+			if (! matched)
+				return false;
 		}
-		return false;
+		return true;
 	}
 
 	/**
@@ -535,9 +543,34 @@ public abstract class ExecutableInfo {
 	 * @return <jk>true</jk> if this method has at most only this arguments in any order.
 	 */
 	public final boolean hasFuzzyParamTypes(Class<?>...args) {
-		return ClassUtils.fuzzyArgsMatch(_getRawParamTypes(), args) != -1;
+		return fuzzyArgsMatch(args) != -1;
+	}
+
+	/**
+	 * Returns how well this method matches the specified arg types.
+	 *
+	 * <p>
+	 * The number returned is the number of method arguments that match the passed in arg types.
+	 * <br>Returns <c>-1</c> if the method cannot take in one or more of the specified arguments.
+	 *
+	 * @param argTypes The arg types to check against.
+	 * @return How many parameters match or <c>-1</c> if method cannot handle one or more of the arguments.
+	 */
+	public int fuzzyArgsMatch(Class<?>... argTypes) {
+		int matches = 0;
+		outer: for (ClassInfo pi : getParamTypes()) {
+			for (Class<?> a : argTypes) {
+				if (pi.isParentOfFuzzyPrimitives(a)) {
+					matches++;
+					continue outer;
+				}
+			}
+			return -1;
+		}
+		return matches;
 	}
 
+
 	/**
 	 * Returns <jk>true</jk> if this method has at most only this arguments in any order.
 	 *
@@ -545,7 +578,31 @@ public abstract class ExecutableInfo {
 	 * @return <jk>true</jk> if this method has at most only this arguments in any order.
 	 */
 	public boolean hasFuzzyParamTypes(ClassInfo...args) {
-		return ClassUtils.fuzzyArgsMatch(_getRawParamTypes(), args) != -1;
+		return fuzzyArgsMatch(args) != -1;
+	}
+
+	/**
+	 * Returns how well this method matches the specified arg types.
+	 *
+	 * <p>
+	 * The number returned is the number of method arguments that match the passed in arg types.
+	 * <br>Returns <c>-1</c> if the method cannot take in one or more of the specified arguments.
+	 *
+	 * @param argTypes The arg types to check against.
+	 * @return How many parameters match or <c>-1</c> if method cannot handle one or more of the arguments.
+	 */
+	public int fuzzyArgsMatch(ClassInfo... argTypes) {
+		int matches = 0;
+		outer: for (ClassInfo pi : getParamTypes()) {
+			for (ClassInfo a : argTypes) {
+				if (pi.isParentOfFuzzyPrimitives(a)) {
+					matches++;
+					continue outer;
+				}
+			}
+			return -1;
+		}
+		return matches;
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoListSwap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoListSwap.java
index 23d06c1..5be0d6a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoListSwap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoListSwap.java
@@ -142,7 +142,7 @@ public class AutoListSwap<T> extends PojoSwap<T,List<?>> {
 	private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) {
 		return
 			cs.isNotDeprecated()
-			&& cs.hasParamTypeParents(rt)
+			&& cs.hasMatchingParamTypes(rt)
 			&& ! bc.hasAnnotation(BeanIgnore.class, cs);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoMapSwap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoMapSwap.java
index ba1b813..34eba9d 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoMapSwap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoMapSwap.java
@@ -142,7 +142,7 @@ public class AutoMapSwap<T> extends PojoSwap<T,Map<?,?>> {
 	private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) {
 		return
 			cs.isNotDeprecated()
-			&& cs.hasParamTypeParents(rt)
+			&& cs.hasMatchingParamTypes(rt)
 			&& ! bc.hasAnnotation(BeanIgnore.class, cs);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoNumberSwap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoNumberSwap.java
index ebb89d6..7954f75 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoNumberSwap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoNumberSwap.java
@@ -168,7 +168,7 @@ public class AutoNumberSwap<T> extends PojoSwap<T,Number> {
 	private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) {
 		return
 			cs.isNotDeprecated()
-			&& cs.hasParamTypeParents(rt)
+			&& cs.hasMatchingParamTypes(rt)
 			&& ! bc.hasAnnotation(BeanIgnore.class, cs);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoObjectSwap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoObjectSwap.java
index aea43c4..2796284 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoObjectSwap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/transform/AutoObjectSwap.java
@@ -143,7 +143,7 @@ public class AutoObjectSwap<T> extends PojoSwap<T,Object> {
 	private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) {
 		return
 			cs.isNotDeprecated()
-			&& cs.hasParamTypeParents(rt)
+			&& cs.hasMatchingParamTypes(rt)
 			&& ! bc.hasAnnotation(BeanIgnore.class, cs);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
index 63f76f5..26ea3ce 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
@@ -259,11 +259,21 @@ public class ReflectionMap<V> {
 			for (ClassEntry<V> e : classEntries)
 				if (e.matches(c))
 					if (ofType == null || ofType.isInstance(e.value))
-						return Optional.of(e.value);
+						return Optional.ofNullable(e.value);
 		return Optional.empty();
 	}
 
 	/**
+	 * Finds first value in this map that matches the specified class.
+	 *
+	 * @param c The class to test for.
+	 * @return The matching object.  Never <jk>null</jk>.
+	 */
+	public Optional<V> find(Class<?> c) {
+		return find(c, null);
+	}
+
+	/**
 	 * Finds all values in this map that matches the specified class.
 	 *
 	 * @param c The class to test for.
@@ -278,6 +288,16 @@ public class ReflectionMap<V> {
 	 * Finds all values in this map that matches the specified class.
 	 *
 	 * @param c The class to test for.
+	 * @return A modifiable list of matching values.  Never <jk>null</jk>.
+	 */
+	public List<V> findAll(Class<?> c) {
+		return appendAll(c, null, null);
+	}
+
+	/**
+	 * Finds all values in this map that matches the specified class.
+	 *
+	 * @param c The class to test for.
 	 * @param ofType Only return objects of the specified type.
 	 * @param l The list to append values to.  Can be <jk>null</jk>.
 	 * @return The same list passed in or a new modifiable list if <jk>null</jk>.
@@ -287,7 +307,7 @@ public class ReflectionMap<V> {
 			l = AList.create();
 		if (! noClassEntries)
 			for (ClassEntry<V> e : classEntries)
-				if (e.matches(c))
+				if (e.matches(c) && e.value != null)
 					if (ofType == null || ofType.isInstance(e.value))
 						l.add(e.value);
 		return l;
@@ -305,11 +325,21 @@ public class ReflectionMap<V> {
 			for (MethodEntry<V> e : methodEntries)
 				if (e.matches(m))
 					if (ofType == null || ofType.isInstance(e.value))
-						return Optional.of(e.value);
+						return Optional.ofNullable(e.value);
 		return Optional.empty();
 	}
 
 	/**
+	 * Finds first value in this map that matches the specified method.
+	 *
+	 * @param m The method to test for.
+	 * @return The matching object.  Never <jk>null</jk>.
+	 */
+	public Optional<V> find(Method m) {
+		return find(m, null);
+	}
+
+	/**
 	 * Finds all values in this map that matches the specified method.
 	 *
 	 * @param m The method to test for.
@@ -324,6 +354,16 @@ public class ReflectionMap<V> {
 	 * Finds all values in this map that matches the specified method.
 	 *
 	 * @param m The method to test for.
+	 * @return A modifiable list of matching values.  Never <jk>null</jk>.
+	 */
+	public List<V> findAll(Method m) {
+		return appendAll(m, null, null);
+	}
+
+	/**
+	 * Finds all values in this map that matches the specified method.
+	 *
+	 * @param m The method to test for.
 	 * @param ofType Only return objects of the specified type.
 	 * @param l The list to append values to.  Can be <jk>null</jk>.
 	 * @return The same list passed in or a new modifiable list if <jk>null</jk>.
@@ -333,7 +373,7 @@ public class ReflectionMap<V> {
 			l = AList.create();
 		if (! noMethodEntries)
 			for (MethodEntry<V> e : methodEntries)
-				if (e.matches(m))
+				if (e.matches(m) && e.value != null)
 					if (ofType == null || ofType.isInstance(e.value))
 						l.add(e.value);
 		return l;
@@ -351,11 +391,21 @@ public class ReflectionMap<V> {
 			for (FieldEntry<V> e : fieldEntries)
 				if (e.matches(f))
 					if (ofType == null || ofType.isInstance(e.value))
-						return Optional.of(e.value);
+						return Optional.ofNullable(e.value);
 		return Optional.empty();
 	}
 
 	/**
+	 * Finds first value in this map that matches the specified field.
+	 *
+	 * @param f The field to test for.
+	 * @return The matching object.  Never <jk>null</jk>.
+	 */
+	public Optional<V> find(Field f) {
+		return find(f, null);
+	}
+
+	/**
 	 * Finds all values in this map that matches the specified field.
 	 *
 	 * @param f The field to test for.
@@ -370,6 +420,16 @@ public class ReflectionMap<V> {
 	 * Finds all values in this map that matches the specified field.
 	 *
 	 * @param f The field to test for.
+	 * @return A modifiable list of matching values.  Never <jk>null</jk>.
+	 */
+	public List<V> findAll(Field f) {
+		return appendAll(f, null, null);
+	}
+
+	/**
+	 * Finds all values in this map that matches the specified field.
+	 *
+	 * @param f The field to test for.
 	 * @param ofType Only return objects of the specified type.
 	 * @param l The list to append values to.  Can be <jk>null</jk>.
 	 * @return The same list passed in or a new modifiable list if <jk>null</jk>.
@@ -379,7 +439,7 @@ public class ReflectionMap<V> {
 			l = AList.create();
 		if (! noFieldEntries)
 			for (FieldEntry<V> e : fieldEntries)
-				if (e.matches(f))
+				if (e.matches(f) && e.value != null)
 					if (ofType == null || ofType.isInstance(e.value))
 						l.add(e.value);
 		return l == null ? new ArrayList<>(0) : l;
@@ -397,11 +457,21 @@ public class ReflectionMap<V> {
 			for (ConstructorEntry<V> e : constructorEntries)
 				if (e.matches(c))
 					if (ofType == null || ofType.isInstance(e.value))
-						return Optional.of(e.value);
+						return Optional.ofNullable(e.value);
 		return Optional.empty();
 	}
 
 	/**
+	 * Finds first value in this map that matches the specified constructor.
+	 *
+	 * @param c The constructor to test for.
+	 * @return The matching object.  Never <jk>null</jk>.
+	 */
+	public Optional<V> find(Constructor<?> c) {
+		return find(c, null);
+	}
+
+	/**
 	 * Finds all values in this map that matches the specified constructor.
 	 *
 	 * @param c The constructor to test for.
@@ -416,6 +486,16 @@ public class ReflectionMap<V> {
 	 * Finds all values in this map that matches the specified constructor.
 	 *
 	 * @param c The constructor to test for.
+	 * @return A modifiable list of matching values.  Never <jk>null</jk>.
+	 */
+	public List<V> findAll(Constructor<?> c) {
+		return appendAll(c, null, null);
+	}
+
+	/**
+	 * Finds all values in this map that matches the specified constructor.
+	 *
+	 * @param c The constructor to test for.
 	 * @param ofType Only return objects of the specified type.
 	 * @param l The list to append values to.  Can be <jk>null</jk>.
 	 * @return The same list passed in or a new modifiable list if <jk>null</jk>.
@@ -425,7 +505,7 @@ public class ReflectionMap<V> {
 			l = AList.create();
 		if (! noConstructorEntries)
 			for (ConstructorEntry<V> e : constructorEntries)
-				if (e.matches(c))
+				if (e.matches(c) && e.value != null)
 					if (ofType == null || ofType.isInstance(e.value))
 						l.add(e.value);
 		return l;
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index 47f87fe..508e1d3 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -166,64 +166,134 @@
 			<li class='jic'>{@link oaj.utils.ClasspathResourceFinder} - Normally defined through {@link oajr.annotation.Rest#classpathResourceFinder() @Rest.classpathResourceFinder()}.
 		</ul>
 	<li>
-		{@link oajr.RestServlet} now implements the {@link oajr.RestCallHandler}, {@link oajr.RestInfoProvider}, and {@link oajr.RestCallLogger}
-		interfaces directly and provides the same functionality as {@link oajr.BasicRestCallHandler}, {@link oajr.BasicRestInfoProvider}, and
-		{@link oajr.BasicRestCallLogger}.  This makes it easier to tweak these methods without having to implement your own classes.
+		{@link oajr.RestServlet} and {@link oajr.BasicRest} now implement the {@link oajr.RestCallHandler}, {@link oajr.RestInfoProvider}, {@link oajr.RestCallLogger}, 
+		and {@link oaj.utils.ClasspathResourceFinder} interfaces directly and provides the same functionality as {@link oajr.BasicRestCallHandler}, 
+		{@link oajr.BasicRestInfoProvider}, {@link oajr.BasicRestCallLogger}, and {@link oaj.utils.ClasspathResourceFinderBasic}.  
+		
+		This makes it easier to tweak these methods without having to implement your own classes.
 		<br>The methods added for {@link oajr.RestCallHandler} are:
 		<ul>
-			<li class='jac'>{@link oaj.RestServlet}
+			<li class='jac'>{@link oajr.RestServlet}
+			<ul>
+				<li class='jm'>{@link oajr.RestServlet#execute(HttpServletRequest, HttpServletResponse) execute(HttpServletRequest, HttpServletResponse)}
+				<li class='jm'>{@link oajr.RestServlet#createCall(HttpServletRequest, HttpServletResponse) createCall(HttpServletRequest, HttpServletResponse)}
+				<li class='jm'>{@link oajr.RestServlet#createRequest(RestCall) createRequest(RestCall)}
+				<li class='jm'>{@link oajr.RestServlet#createResponse(RestCall) createResponse(RestCall)}
+				<li class='jm'>{@link oajr.RestServlet#handleResponse(RestCall) handleResponse(RestCall)}
+				<li class='jm'>{@link oajr.RestServlet#handleNotFound(RestCall) handleNotFound(RestCall)}
+				<li class='jm'>{@link oajr.RestServlet#handleError(RestCall,Throwable) handleError(RestCall,Throwable)}
+				<li class='jm'>{@link oajr.RestServlet#convertThrowable(Throwable) throwable(Throwable)}
+				<li class='jm'>{@link oajr.RestServlet#getSessionObjects(RestRequest req, RestResponse res) getSessionObjects(RestRequest,RestResponse)}
+			</ul>
+			<li class='jac'>{@link oajr.BasicRest}
 			<ul>
-				<li class='jm'>{@link oaj.RestServlet#execute(HttpServletRequest, HttpServletResponse) execute(HttpServletRequest, HttpServletResponse)}
-				<li class='jm'>{@link oaj.RestServlet#createCall(HttpServletRequest, HttpServletResponse) createCall(HttpServletRequest, HttpServletResponse)}
-				<li class='jm'>{@link oaj.RestServlet#createRequest(RestCall) createRequest(RestCall)}
-				<li class='jm'>{@link oaj.RestServlet#createResponse(RestCall) createResponse(RestCall)}
-				<li class='jm'>{@link oaj.RestServlet#handleResponse(RestCall) handleResponse(RestCall)}
-				<li class='jm'>{@link oaj.RestServlet#handleNotFound(RestCall) handleNotFound(RestCall)}
-				<li class='jm'>{@link oaj.RestServlet#handleError(RestCall,Throwable) handleError(RestCall,Throwable)}
-				<li class='jm'>{@link oaj.RestServlet#convertThrowable(Throwable) throwable(Throwable)}
-				<li class='jm'>{@link oaj.RestServlet#getSessionObjects(RestRequest req, RestResponse res) getSessionObjects(RestRequest,RestResponse)}
+				<li class='jm'>{@link oajr.BasicRest#execute(HttpServletRequest, HttpServletResponse) execute(HttpServletRequest, HttpServletResponse)}
+				<li class='jm'>{@link oajr.BasicRest#createCall(HttpServletRequest, HttpServletResponse) createCall(HttpServletRequest, HttpServletResponse)}
+				<li class='jm'>{@link oajr.BasicRest#createRequest(RestCall) createRequest(RestCall)}
+				<li class='jm'>{@link oajr.BasicRest#createResponse(RestCall) createResponse(RestCall)}
+				<li class='jm'>{@link oajr.BasicRest#handleResponse(RestCall) handleResponse(RestCall)}
+				<li class='jm'>{@link oajr.BasicRest#handleNotFound(RestCall) handleNotFound(RestCall)}
+				<li class='jm'>{@link oajr.BasicRest#handleError(RestCall,Throwable) handleError(RestCall,Throwable)}
+				<li class='jm'>{@link oajr.BasicRest#convertThrowable(Throwable) throwable(Throwable)}
+				<li class='jm'>{@link oajr.BasicRest#getSessionObjects(RestRequest req, RestResponse res) getSessionObjects(RestRequest,RestResponse)}
 			</ul>
 		</ul>
 		<br>The methods added for {@link oajr.RestInfoProvider} are:
 		<ul>
-			<li class='jac'>{@link oaj.RestServlet}
+			<li class='jac'>{@link oajr.RestServlet}
+			<ul>
+				<li class='jm'>{@link oajr.RestServlet#getSwagger(RestRequest) getSwagger(RestRequest)}
+				<li class='jm'>{@link oajr.RestServlet#getSiteName(RestRequest) getSiteName(RestRequest)}
+				<li class='jm'>{@link oajr.RestServlet#getTitle(RestRequest) getTitle(RestRequest)}
+				<li class='jm'>{@link oajr.RestServlet#getDescription(RestRequest) getDescription(RestRequest)}
+				<li class='jm'>{@link oajr.RestServlet#getMethodSummary(Method,RestRequest) getMethodSummary(Method,RestRequest)}
+				<li class='jm'>{@link oajr.RestServlet#getMethodDescription(Method,RestRequest) getMethodDescription(Method,RestRequest)}
+			</ul>
+			<li class='jac'>{@link oajr.BasicRest}
 			<ul>
-				<li class='jm'>{@link oaj.RestServlet#getSwagger(RestRequest) getSwagger(RestRequest)}
-				<li class='jm'>{@link oaj.RestServlet#getSiteName(RestRequest) getSiteName(RestRequest)}
-				<li class='jm'>{@link oaj.RestServlet#getTitle(RestRequest) getTitle(RestRequest)}
-				<li class='jm'>{@link oaj.RestServlet#getDescription(RestRequest) getDescription(RestRequest)}
-				<li class='jm'>{@link oaj.RestServlet#getMethodSummary(Method,RestRequest) getMethodSummary(Method,RestRequest)}
-				<li class='jm'>{@link oaj.RestServlet#getMethodDescription(Method,RestRequest) getMethodDescription(Method,RestRequest)}
+				<li class='jm'>{@link oajr.BasicRest#getSwagger(RestRequest) getSwagger(RestRequest)}
+				<li class='jm'>{@link oajr.BasicRest#getSiteName(RestRequest) getSiteName(RestRequest)}
+				<li class='jm'>{@link oajr.BasicRest#getTitle(RestRequest) getTitle(RestRequest)}
+				<li class='jm'>{@link oajr.BasicRest#getDescription(RestRequest) getDescription(RestRequest)}
+				<li class='jm'>{@link oajr.BasicRest#getMethodSummary(Method,RestRequest) getMethodSummary(Method,RestRequest)}
+				<li class='jm'>{@link oajr.BasicRest#getMethodDescription(Method,RestRequest) getMethodDescription(Method,RestRequest)}
 			</ul>
 		</ul>
 		<br>The methods added for {@link oajr.RestCallLogger} are:
 		<ul>
-			<li class='jac'>{@link oaj.RestServlet}
+			<li class='jac'>{@link oajr.RestServlet}
 			<ul>
-				<li class='jm'>{@link oaj.RestServlet#log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse) log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse)}
+				<li class='jm'>{@link oajr.RestServlet#log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse) log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse)}
+			</ul>
+			<li class='jac'>{@link oajr.BasicRest}
+			<ul>
+				<li class='jm'>{@link oajr.BasicRest#log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse) log(RestCallLoggerConfig,HttpServletRequest,HttpServletResponse)}
 			</ul>
 		</ul>
 		<br>The methods added for {@link oaj.utils.ClassspathResourceFinder} are:
 		<ul>
-			<li class='jac'>{@link oaj.RestServlet}
+			<li class='jac'>{@link oajr.RestServlet}
+			<ul>
+				<li class='jm'>{@link oajr.RestServlet#findResource(Class,String,Locale) findResource(Class,String,Locale)}
+			</ul>
+			<li class='jac'>{@link oajr.BasicRest}
 			<ul>
-				<li class='jm'>{@link oaj.RestServlet#findResource(Class,String,Locale) findResource(Class,String,Locale)}
+				<li class='jm'>{@link oajr.BasicRest#findResource(Class,String,Locale) findResource(Class,String,Locale)}
 			</ul>
 		</ul>
 	<li>
-		Added the following convenience hook methods on the {@link RestServlet} class:
+		Added the following convenience hook methods on the {@link oajr.RestServlet} and {@link oajr.BasicRest} classes:
 		<ul>
-			<li class='jac'>{@link oaj.RestServlet}
+			<li class='jac'>{@link oajr.RestServlet}
 			<ul>
-				<li class='jm'>{@link oaj.RestServlet#onInit(RestContextBuilder) onInit(RestContextBuilder)}
-				<li class='jm'>{@link oaj.RestServlet#onPostInit(RestContext) onPostInit(RestContext)}
-				<li class='jm'>{@link oaj.RestServlet#onPostInitChildFirst(RestContext) onPostInitChildFirst(RestContext)}
-				<li class='jm'>{@link oaj.RestServlet#onDestroy(RestContext) onDestroy(RestContext)}
-				<li class='jm'>{@link oaj.RestServlet#onStartCall(HttpServletRequest,HttpServletResponse) onStartCall(HttpServletRequest,HttpServletResponse)}
-				<li class='jm'>{@link oaj.RestServlet#onPreCall(RestRequest,RestResponse) onPreCall(RestRequest,RestResponse)}
-				<li class='jm'>{@link oaj.RestServlet#onPostCall(RestRequest,RestResponse) onPostCall(RestRequest,RestResponse)}
-				<li class='jm'>{@link oaj.RestServlet#onEndCall(HttpServletRequest,HttpServletResponse) onEndCall(HttpServletRequest,HttpServletResponse)}
+				<li class='jm'>{@link oajr.RestServlet#onInit(RestContextBuilder) onInit(RestContextBuilder)}
+				<li class='jm'>{@link oajr.RestServlet#onPostInit(RestContext) onPostInit(RestContext)}
+				<li class='jm'>{@link oajr.RestServlet#onPostInitChildFirst(RestContext) onPostInitChildFirst(RestContext)}
+				<li class='jm'>{@link oajr.RestServlet#onDestroy(RestContext) onDestroy(RestContext)}
+				<li class='jm'>{@link oajr.RestServlet#onStartCall(HttpServletRequest,HttpServletResponse) onStartCall(HttpServletRequest,HttpServletResponse)}
+				<li class='jm'>{@link oajr.RestServlet#onPreCall(RestRequest,RestResponse) onPreCall(RestRequest,RestResponse)}
+				<li class='jm'>{@link oajr.RestServlet#onPostCall(RestRequest,RestResponse) onPostCall(RestRequest,RestResponse)}
+				<li class='jm'>{@link oajr.RestServlet#onEndCall(HttpServletRequest,HttpServletResponse) onEndCall(HttpServletRequest,HttpServletResponse)}
 			</ul>
+			<li class='jac'>{@link oajr.BasicRest}
+			<ul>
+				<li class='jm'>{@link oajr.BasicRest#onInit(RestContextBuilder) onInit(RestContextBuilder)}
+				<li class='jm'>{@link oajr.BasicRest#onPostInit(RestContext) onPostInit(RestContext)}
+				<li class='jm'>{@link oajr.BasicRest#onPostInitChildFirst(RestContext) onPostInitChildFirst(RestContext)}
+				<li class='jm'>{@link oajr.BasicRest#onDestroy(RestContext) onDestroy(RestContext)}
+				<li class='jm'>{@link oajr.BasicRest#onStartCall(HttpServletRequest,HttpServletResponse) onStartCall(HttpServletRequest,HttpServletResponse)}
+				<li class='jm'>{@link oajr.BasicRest#onPreCall(RestRequest,RestResponse) onPreCall(RestRequest,RestResponse)}
+				<li class='jm'>{@link oajr.BasicRest#onPostCall(RestRequest,RestResponse) onPostCall(RestRequest,RestResponse)}
+				<li class='jm'>{@link oajr.BasicRest#onEndCall(HttpServletRequest,HttpServletResponse) onEndCall(HttpServletRequest,HttpServletResponse)}
+			</ul>
+		</ul>
+	<li>
+		New {@link Rest#debugOn() @Rest(debugOn)} annotation for turning on debug mode using class/method identifiers:
+		
+		<h5 class='figure'>Example:</h5>
+		<p class='bcode w800'>
+	<jc>// Turn on debug per-request on the class and always on the doX() method</jc>.
+	<ja>@Rest</ja>(
+		debugOn=<js>"MyResource=per-request,Mysource.doX=true"</js>  <jc>// Typically defined via system or env property</jc>.
+	)
+	<jk>public class</jk> MyResource {
+
+		<ja>@RestMethod</ja>
+		<jk>public void</jk> String doX() {
+			...
+		}
+		 </p>
+	<li>
+		{@link oajr.BasicRestConfig} has been broken up into {@link oajr.BasicRestConfig} and {@link oajr.BasicRestMethods} so that
+		you're not forced to implement methods such as <c>getOptions()</c> and <c>getStats()</c> if you're implementing the interface
+		to configure your REST class.
+	<li>
+		Any of the following classes can now be instantiated with <c><jk>public static</jk> <jsm>create</jsm>()</c> methods:
+		<ul>
+			<li class='jc'>{@link oajr.RestCallHandler}
+			<li class='jc'>{@link oajr.RestCallLogger}
+			<li class='jc'>{@link oajr.RestInfoProvider}
+			<li class='jc'>{@link oaj.utils.ClasspathResourceFinder}
 		</ul>
 </ul>
 
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
index 9d9658b..237c353 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
@@ -14,9 +14,7 @@ package org.apache.juneau.examples.rest;
 
 import static org.apache.juneau.http.HttpMethodName.*;
 
-import org.apache.juneau.dto.swagger.*;
 import org.apache.juneau.html.annotation.*;
-import org.apache.juneau.http.exception.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 
@@ -51,27 +49,4 @@ public class HelloWorldResource implements BasicRestConfig {
 	public String sayHello() {
 		return "Hello world!";
 	}
-
-	/**
-	 * Make a request to /helloWorld/badRequest to trigger a 400 Bad Request.
-	 *
-	 * @throws BadRequest A bad request.
-	 */
-	@RestMethod
-	public void getBadRequest() throws BadRequest {
-		throw new BadRequest("example");
-	}
-
-	@Override /* BasicRestConfig */
-	public Swagger getOptions(RestRequest req) {
-		return req.getSwagger();
-	}
-
-	@Override /* BasicRestConfig */
-	public void error() {}
-
-	@Override /* BasicRestConfig */
-	public RestContextStats getStats(RestRequest req) {
-		return req.getContext().getStats();
-	}
 }
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java
index e8f332c..82e0849 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockServletRequest.java
@@ -1471,7 +1471,8 @@ public class MockServletRequest implements HttpServletRequest, MockHttpRequest {
 	 */
 	public MockServletRequest debug(boolean value) {
 		this.debug = value;
-		header("X-Debug", value ? true : null);
+		if (value)
+			header("X-Debug", true);
 		return this;
 	}
 
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/DebugModeTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/DebugModeTest.java
new file mode 100644
index 0000000..41bed60
--- /dev/null
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/DebugModeTest.java
@@ -0,0 +1,1195 @@
+// ***************************************************************************************************************************
+// * 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.junit.Assert.*;
+
+import java.util.logging.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.mock2.*;
+import org.junit.*;
+import org.junit.runners.*;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class DebugModeTest {
+
+	public static final CaptureCallLogger LOGGER = new CaptureCallLogger();
+
+	public static class CaptureCallLogger extends BasicRestCallLogger {
+
+		private volatile String msg;
+
+		public static CaptureCallLogger create() {
+			return LOGGER;
+		}
+
+		private CaptureCallLogger() {
+			super(null);
+		}
+
+		@Override
+		protected synchronized void log(Level level, String msg, Throwable e) {
+			this.msg = StringUtils.emptyIfNull(msg);
+		}
+
+		synchronized CaptureCallLogger reset() {
+			this.msg = null;
+			return this;
+		}
+
+		synchronized String getMessage() {
+			return msg;
+		}
+
+		public synchronized CaptureCallLogger assertMessageContains(String s) {
+			assertNotNull(msg);
+			if (! msg.contains(s))
+				assertEquals("Substring not found.", s, msg);
+			return this;
+		}
+	}
+
+	private static void assertLogged(boolean b) {
+		assertEquals(b, LOGGER.getMessage() != null);
+		LOGGER.reset();
+	}
+
+	private static void assertLoggedContains(String s) {
+		String msg = LOGGER.getMessage();
+		assertLogged(true);
+		if (! msg.contains(s))
+			assertEquals("Substring not found.", s, msg);
+		LOGGER.reset();
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// @Rest(debug=""), various @RestMethod(debug)
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(callLogger=CaptureCallLogger.class)
+	public static class A1 implements BasicRestConfig {
+		@RestMethod
+		public boolean getA01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getA02(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getA03(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getA04(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="foo")
+		public boolean getA05(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA06(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA07(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest a1 = MockRest.build(A1.class);
+	static MockRest a1d = MockRest.create(A1.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void a01_debugDefault() throws Exception {
+
+		assertEquals("false", a1.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1d.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("false", a1.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1d.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a1.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+		assertEquals("true", a1d.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+
+		assertEquals("false", a1.get("/a04").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a1d.get("/a04").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a04");
+
+		assertEquals("false", a1.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1d.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a1.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a1d.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a1.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1d.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+	@Rest(callLogger=CaptureCallLogger.class)
+	public static class A1a extends BasicRest {
+		@RestMethod
+		public boolean getA01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getA02(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getA03(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getA04(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="foo")
+		public boolean getA05(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA06(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA07(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest a1a = MockRest.build(A1a.class);
+	static MockRest a1ad = MockRest.create(A1a.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void a01a_debugDefault() throws Exception {
+
+		assertEquals("false", a1a.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1ad.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("false", a1a.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1ad.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a1a.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+		assertEquals("true", a1ad.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+
+		assertEquals("false", a1a.get("/a04").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a1ad.get("/a04").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a04");
+
+		assertEquals("false", a1a.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1ad.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a1a.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a1ad.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a1a.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a1ad.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// @Rest(debug="true"), various @RestMethod(debug)
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(callLogger=CaptureCallLogger.class, debug="true")
+	public static class A2 implements BasicRestConfig {
+		@RestMethod
+		public boolean getA01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getA02(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getA03(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getA04(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="foo")
+		public boolean getA05(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA06(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA07(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest a2 = MockRest.build(A2.class);
+	static MockRest a2d = MockRest.create(A2.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void a02_debugTrue() throws Exception {
+
+		assertEquals("true", a2.get("/a01").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a2d.get("/a01").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a2.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a2d.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a2.get("/a03").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a2d.get("/a03").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a2.get("/a04").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a2d.get("/a04").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("true", a2.get("/a05").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a2d.get("/a05").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("true", a2.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a2d.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a2.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a2d.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// @Rest(debug="false"), various @RestMethod(debug)
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(callLogger=CaptureCallLogger.class,debug="false")
+	public static class A3 implements BasicRestConfig {
+		@RestMethod
+		public boolean getA01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getA02(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getA03(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getA04(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="foo")
+		public boolean getA05(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA06(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA07(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest a3 = MockRest.build(A3.class);
+	static MockRest a3d = MockRest.create(A3.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void a03_restDebugFalse() throws Exception {
+
+		assertEquals("false", a3.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a3d.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("false", a3.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a3d.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a3.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+		assertEquals("true", a3d.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+
+		assertEquals("false", a3.get("/a04").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a3d.get("/a04").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a04");
+
+		assertEquals("false", a3.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a3d.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a3.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a3d.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a3.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a3d.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// @Rest(debug="per-request"), various @RestMethod(debug)
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(callLogger=CaptureCallLogger.class,debug="per-request")
+	public static class A4 implements BasicRestConfig {
+		@RestMethod
+		public boolean getA01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getA02(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getA03(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getA04(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="foo")
+		public boolean getA05(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA06(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getA07(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest a4 = MockRest.build(A4.class);
+	static MockRest a4d = MockRest.create(A4.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void a04_debugPerRequest() throws Exception {
+
+		assertEquals("false", a4.get("/a01").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a4d.get("/a01").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a4.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a4d.get("/a02").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", a4.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+		assertEquals("true", a4d.get("/a03").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a03");
+
+		assertEquals("false", a4.get("/a04").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a4d.get("/a04").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /a04");
+
+		assertEquals("false", a4.get("/a05").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", a4d.get("/a05").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("true", a4.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", a4d.get("/a06").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", a4.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", a4d.get("/a07").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Implement RestCallLogger directly.
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest
+	public static class B1 implements BasicRestConfig, RestCallLogger {
+		@RestMethod
+		public boolean getB01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getB02(RestRequest req) {
+			return req.isDebug();
+		}
+		@Override
+		public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) {
+			LOGGER.log(config, req, res);
+		}
+	}
+	static MockRest b1 = MockRest.build(B1.class);
+
+	@Test
+	public void b01_debugDefault() throws Exception {
+
+		assertEquals("false", b1.get("/b01").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", b1.get("/b02").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /b02");
+	}
+
+	@Rest
+	public static class B2 extends BasicRest {
+		@RestMethod
+		public boolean getB01(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getB02(RestRequest req) {
+			return req.isDebug();
+		}
+		@Override
+		public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) {
+			LOGGER.log(config, req, res);
+		}
+	}
+	static MockRest b2 = MockRest.build(B2.class);
+
+	@Test
+	public void b02_debugDefault() throws Exception {
+
+		assertEquals("false", b2.get("/b01").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", b2.get("/b02").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /b02");
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// @Rest(debugOn=""), various @RestMethod(debug)
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(
+		callLogger=CaptureCallLogger.class,
+		debugOn=""
+			+ "C1.getC02a=false,C1.getC02b=false,C1.getC02c=FALSE,C1.getC02d=FALSE,C1.getC02e=FALSE,C1.getC02f=FALSE,"
+			+ " C1.getC03a , C1.getC03b = true , C1.getC03c = TRUE , C1.getC03d = TRUE , C1.getC03e = TRUE , C1.getC03f = TRUE , "
+			+ "C1.getC04a=per-request,C1.getC04b=per-request,C1.getC04c=PER-REQUEST,C1.getC04d=PER-REQUEST,C1.getC04e=PER-REQUEST,C1.getC04f=PER-REQUEST,"
+			+ "C1.getC05a=foo,C1.getC05b=,C1.getC05c=foo,C1.getC05d=foo,C1.getC05e=foo,C1.getC05f=foo,"
+	)
+	public static class C1 implements BasicRestConfig {
+
+		@RestMethod
+		public boolean getC01a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC01b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC01c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC01d(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=false
+		@RestMethod
+		public boolean getC02a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC02b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC02c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC02d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC02e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC02f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=true
+		@RestMethod
+		public boolean getC03a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC03b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC03c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC03d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC03e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC03f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=per-request
+		@RestMethod
+		public boolean getC04a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC04b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC04c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC04d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC04e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC04f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=foo
+		@RestMethod
+		public boolean getC05a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC05b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC05c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC05d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC05e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC05f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		@RestMethod
+		public boolean getC06a(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC06b(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC06c(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC06d(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+
+		@RestMethod
+		public boolean getC07a(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC07b(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC07c(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC07d(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest c1 = MockRest.build(C1.class);
+	static MockRest c1d = MockRest.create(C1.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void c01_debugDefault() throws Exception {
+
+		assertEquals("false", c1.get("/c01a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c01a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c01b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c01b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1.get("/c01c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1d.get("/c01c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("false", c1.get("/c01d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c01d").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", c1.get("/c02a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c02a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c02b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c02b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c02c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c02c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c02d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c02d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c02e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c02e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c02f").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c02f").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", c1.get("/c03a").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03a");
+		assertEquals("true", c1d.get("/c03a").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03a");
+		assertEquals("true", c1.get("/c03b").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03b");
+		assertEquals("true", c1d.get("/c03b").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03b");
+		assertEquals("true", c1.get("/c03c").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03c");
+		assertEquals("true", c1d.get("/c03c").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03c");
+		assertEquals("true", c1.get("/c03d").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03d");
+		assertEquals("true", c1d.get("/c03d").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03d");
+		assertEquals("true", c1.get("/c03e").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03e");
+		assertEquals("true", c1d.get("/c03e").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03e");
+		assertEquals("true", c1.get("/c03f").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03f");
+		assertEquals("true", c1d.get("/c03f").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03f");
+
+		assertEquals("false", c1.get("/c04a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c04a").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04a");
+		assertEquals("false", c1.get("/c04b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c04b").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04b");
+		assertEquals("false", c1.get("/c04c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c04c").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04c");
+		assertEquals("false", c1.get("/c04d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c04d").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04d");
+		assertEquals("false", c1.get("/c04e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c04e").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04e");
+		assertEquals("false", c1.get("/c04f").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c1d.get("/c04f").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04f");
+
+		assertEquals("false", c1.get("/c05a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c05a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c05b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c05b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c05c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c05c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c05d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c05d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c05e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c05e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c05f").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c05f").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", c1.get("/c06a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1d.get("/c06a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1.get("/c06b").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1d.get("/c06b").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1.get("/c06c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1d.get("/c06c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1.get("/c06d").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c1d.get("/c06d").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", c1.get("/c07a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c07a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c07b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c07b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c07c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c07c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1.get("/c07d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c1d.get("/c07d").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+	static {
+		System.setProperty("C2DebugEnabled", "C2=true");
+	}
+	@Rest(
+		callLogger=CaptureCallLogger.class,
+		debugOn="$S{C2DebugEnabled},"
+			+ "C2.getC02a=false,C2.getC02b=false,C2.getC02c=FALSE,C2.getC02d=FALSE,C2.getC02e=FALSE,C2.getC02f=FALSE,"
+			+ " C2.getC03a , C2.getC03b = true , C2.getC03c = TRUE , C2.getC03d = TRUE , C2.getC03e = TRUE , C2.getC03f = TRUE , "
+			+ "C2.getC04a=per-request,C2.getC04b=per-request,C2.getC04c=PER-REQUEST,C2.getC04d=PER-REQUEST,C2.getC04e=PER-REQUEST,C2.getC04f=PER-REQUEST,"
+			+ "C2.getC05a=foo,C2.getC05b=,C2.getC05c=foo,C2.getC05d=foo,C2.getC05e=foo,C2.getC05f=foo,"
+	)
+	public static class C2 implements BasicRestConfig {
+
+		@RestMethod
+		public boolean getC01a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC01b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC01c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC01d(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=false
+		@RestMethod
+		public boolean getC02a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC02b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC02c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC02d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC02e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC02f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=true
+		@RestMethod
+		public boolean getC03a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC03b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC03c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC03d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC03e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC03f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=per-request
+		@RestMethod
+		public boolean getC04a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC04b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC04c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC04d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC04e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC04f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		// debug=foo
+		@RestMethod
+		public boolean getC05a(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC05b(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod
+		public boolean getC05c(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC05d(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC05e(RestRequest req) {
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC05f(RestRequest req) {
+			return req.isDebug();
+		}
+
+		@RestMethod
+		public boolean getC06a(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC06b(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC06c(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC06d(RestRequest req) throws Exception {
+			req.setDebug();
+			return req.isDebug();
+		}
+
+		@RestMethod
+		public boolean getC07a(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+		@RestMethod(debug="false")
+		public boolean getC07b(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+		@RestMethod(debug="true")
+		public boolean getC07c(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+		@RestMethod(debug="per-request")
+		public boolean getC07d(RestRequest req) throws Exception {
+			req.setDebug(false);
+			return req.isDebug();
+		}
+	}
+	static MockRest c2 = MockRest.build(C2.class);
+	static MockRest c2d = MockRest.create(C2.class).simpleJson().header("X-Debug", true).build();
+
+	@Test
+	public void c02_debugTrue() throws Exception {
+
+		assertEquals("true", c2.get("/c01a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c01a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("false", c2.get("/c01b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c01b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2.get("/c01c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c01c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("false", c2.get("/c01d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c01d").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", c2.get("/c02a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c02a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c02b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c02b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c02c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c02c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c02d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c02d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c02e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c02e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c02f").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c02f").execute().getBodyAsString());
+		assertLogged(false);
+
+		assertEquals("true", c2.get("/c03a").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03a");
+		assertEquals("true", c2d.get("/c03a").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03a");
+		assertEquals("true", c2.get("/c03b").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03b");
+		assertEquals("true", c2d.get("/c03b").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03b");
+		assertEquals("true", c2.get("/c03c").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03c");
+		assertEquals("true", c2d.get("/c03c").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03c");
+		assertEquals("true", c2.get("/c03d").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03d");
+		assertEquals("true", c2d.get("/c03d").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03d");
+		assertEquals("true", c2.get("/c03e").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03e");
+		assertEquals("true", c2d.get("/c03e").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03e");
+		assertEquals("true", c2.get("/c03f").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03f");
+		assertEquals("true", c2d.get("/c03f").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c03f");
+
+		assertEquals("false", c2.get("/c04a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c04a").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04a");
+		assertEquals("false", c2.get("/c04b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c04b").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04b");
+		assertEquals("false", c2.get("/c04c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c04c").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04c");
+		assertEquals("false", c2.get("/c04d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c04d").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04d");
+		assertEquals("false", c2.get("/c04e").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c04e").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04e");
+		assertEquals("false", c2.get("/c04f").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("true", c2d.get("/c04f").execute().getBodyAsString());
+		assertLoggedContains("[200] HTTP GET /c04f");
+
+		assertEquals("true", c2.get("/c05a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c05a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c05b").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c05b").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c05c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c05c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c05d").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c05d").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c05e").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c05e").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c05f").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c05f").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("true", c2.get("/c06a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c06a").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c06b").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c06b").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c06c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c06c").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2.get("/c06d").execute().getBodyAsString());
+		assertLogged(true);
+		assertEquals("true", c2d.get("/c06d").execute().getBodyAsString());
+		assertLogged(true);
+
+		assertEquals("false", c2.get("/c07a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c07a").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c07b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c07b").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c07c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c07c").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2.get("/c07d").execute().getBodyAsString());
+		assertLogged(false);
+		assertEquals("false", c2d.get("/c07d").execute().getBodyAsString());
+		assertLogged(false);
+	}
+
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java
index f7f5c7f..65c4515 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRest.java
@@ -12,15 +12,22 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest;
 
+import static org.apache.juneau.rest.annotation.HookEvent.*;
+
+import java.io.*;
+import java.lang.reflect.Method;
 import java.text.*;
+import java.util.*;
 import java.util.logging.*;
 
+import javax.servlet.*;
 import javax.servlet.http.*;
 
 import org.apache.juneau.dto.swagger.*;
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.utils.*;
 import org.apache.juneau.http.exception.*;
 
 /**
@@ -42,29 +49,35 @@ import org.apache.juneau.http.exception.*;
 		"stats: servlet:/stats"
 	}
 )
-public abstract class BasicRest implements BasicRestConfig {
+public abstract class BasicRest implements BasicRestConfig, BasicRestMethods, RestCallHandler, RestInfoProvider, RestCallLogger, ClasspathResourceFinder {
 
 	private JuneauLogger logger = JuneauLogger.getLogger(getClass());
 	private volatile RestContext context;
+	private RestCallHandler callHandler;
+	private RestInfoProvider infoProvider;
+	private RestCallLogger callLogger;
+	private ClasspathResourceFinder resourceFinder;
 
 	/**
-	 * Post-initialization hook to retrieve the {@link RestContext} object for this resource.
+	 * [OPTIONS /*] - Show resource options.
 	 *
-	 * @param context The context for this resource.
+	 * @param req The HTTP request.
+	 * @return A bean containing the contents for the OPTIONS page.
 	 */
-	@RestHook(HookEvent.POST_INIT)
-	public synchronized void onPostInit(RestContext context) {
-		this.context = context;
+	@Override /* BasicRestConfig */
+	public Swagger getOptions(RestRequest req) {
+		// Localized Swagger for this resource is available through the RestRequest object.
+		return req.getSwagger();
 	}
 
 	/**
-	 * [OPTIONS /*] - Show resource options.
+	 * [GET /options] - Show resource options.
 	 *
 	 * @param req The HTTP request.
 	 * @return A bean containing the contents for the OPTIONS page.
 	 */
 	@Override /* BasicRestConfig */
-	public Swagger getOptions(RestRequest req) {
+	public Swagger getOptions2(RestRequest req) {
 		// Localized Swagger for this resource is available through the RestRequest object.
 		return req.getSwagger();
 	}
@@ -169,13 +182,292 @@ public abstract class BasicRest implements BasicRestConfig {
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
-	// Request-time methods.
+	// Hook events
+	//-----------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Method that gets called during servlet initialization.
+	 *
+	 * <p>
+	 * This method is called from within the {@link Servlet#init(ServletConfig)} method after the {@link RestContextBuilder}
+	 * object has been created and initialized with the annotations defined on the class, but before the
+	 * {@link RestContext} object has been created.
+	 *
+	 * <p>
+	 * An example of this is the <c>PetStoreResource</c> class that uses an init method to perform initialization
+	 * of an internal data structure.
+	 *
+	 * <h5 class='figure'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<ja>@Rest</ja>(...)
+	 * 	<jk>public class</jk> PetStoreResource <jk>extends</jk> ResourceJena {
+	 *
+	 * 		<jc>// Our database.</jc>
+	 * 		<jk>private</jk> Map&lt;Integer,Pet&gt; <jf>petDB</jf>;
+	 *
+	 * 		<ja>@Override</ja>
+	 * 		<jk>public void</jk> onInit(RestContextBuilder builder) <jk>throws</jk> Exception {
+	 * 			<jc>// Load our database from a local JSON file.</jc>
+	 * 			<jf>petDB</jf> = JsonParser.<jsf>DEFAULT</jsf>.parse(getClass().getResourceAsStream(<js>"PetStore.json"</js>), LinkedHashMap.<jk>class</jk>, Integer.<jk>class</jk>, Pet.<jk>class</jk>);
+	 * 		}
+	 * 	}
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple INIT methods can be defined on a class.
+	 * 		<br>INIT methods on parent classes are invoked before INIT methods on child classes.
+	 * 		<br>The order of INIT method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception causing initialization of the servlet to fail.
+	 * </ul>
+	 *
+	 * @param builder Context builder which can be used to configure the servlet.
+	 * @throws Exception Any exception thrown will cause servlet to fail startup.
+	 */
+	@RestHook(INIT)
+	public void onInit(RestContextBuilder builder) throws Exception {}
+
+	/**
+	 * Method that gets called immediately after servlet initialization.
+	 *
+	 * <p>
+	 * This method is called from within the {@link Servlet#init(ServletConfig)} method after the {@link RestContext}
+	 * object has been created.
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple POST_INIT methods can be defined on a class.
+	 * 		<br>POST_INIT methods on parent classes are invoked before POST_INIT methods on child classes.
+	 * 		<br>The order of POST_INIT method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception causing initialization of the servlet to fail.
+	 * </ul>
+	 *
+	 * @param context The initialized context object.
+	 * @throws Exception Any exception thrown will cause servlet to fail startup.
+	 */
+	@RestHook(POST_INIT)
+	public void onPostInit(RestContext context) throws Exception {
+		this.context = context;
+		this.callHandler = new BasicRestCallHandler(context);
+		this.infoProvider = new BasicRestInfoProvider(context);
+		this.callLogger = new BasicRestCallLogger(context);
+		this.resourceFinder = new ClasspathResourceFinderBasic();
+	}
+
+	/**
+	 * Identical to {@link #onPostInit(RestContext)} except the order of execution is child-resources first.
+	 *
+	 * <p>
+	 * Use this method if you need to perform any kind of initialization on child resources before the parent resource.
+	 *
+	 * <p>
+	 * This method is called from within the {@link Servlet#init(ServletConfig)} method after the {@link RestContext}
+	 * object has been created and after the {@link #POST_INIT} methods have been called.
+	 *
+	 * <p>
+	 * The only valid parameter type for this method is {@link RestContext} which can be used to retrieve information
+	 * about the servlet.
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple POST_INIT_CHILD_FIRST methods can be defined on a class.
+	 * 		<br>POST_INIT_CHILD_FIRST methods on parent classes are invoked before POST_INIT_CHILD_FIRST methods on child classes.
+	 * 		<br>The order of POST_INIT_CHILD_FIRST method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception causing initialization of the servlet to fail.
+	 * </ul>
+	 *
+	 * @param context The initialized context object.
+	 * @throws Exception Any exception thrown will cause servlet to fail startup.
+	 */
+	@RestHook(POST_INIT_CHILD_FIRST)
+	public void onPostInitChildFirst(RestContext context) throws Exception {}
+
+	/**
+	 * Method that gets called during servlet destroy.
+	 *
+	 * <p>
+	 * This method is called from within the {@link Servlet#destroy()}.
+	 *
+	 * <h5 class='figure'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<ja>@Rest</ja>(...)
+	 * 	<jk>public class</jk> PetStoreResource <jk>extends</jk> ResourceJena {
+	 *
+	 * 		<jc>// Our database.</jc>
+	 * 		<jk>private</jk> Map&lt;Integer,Pet&gt; <jf>petDB</jf>;
+	 *
+	 * 		<ja>@Override</ja>
+	 * 		<jk>public void</jk> onDestroy(RestContext context) {
+	 * 			<jf>petDB</jf> = <jk>null</jk>;
+	 * 		}
+	 * 	}
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple DESTROY methods can be defined on a class.
+	 * 		<br>DESTROY methods on child classes are invoked before DESTROY methods on parent classes.
+	 * 		<br>The order of DESTROY method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		In general, destroy methods should not throw any exceptions, although if any are thrown, the stack trace will be
+	 * 		printed to <c>System.err</c>.
+	 * </ul>
+	 *
+	 * @param context The initialized context object.
+	 * @throws Exception Any exception thrown will cause stack trace to be printed to <c>System.err</c>.
+	 */
+	@RestHook(DESTROY)
+	public void onDestroy(RestContext context) throws Exception {}
+
+	/**
+	 * A method that is called immediately after the <c>HttpServlet.service(HttpServletRequest, HttpServletResponse)</c>
+	 * method is called.
+	 *
+	 * <p>
+	 * Note that you only have access to the raw request and response objects at this point.
+	 *
+	 * <h5 class='figure'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<ja>@Rest</ja>(...)
+	 * 	<jk>public class</jk> MyResource <jk>extends</jk> BasicRestServlet {
+	 *
+	 * 		<jc>// Add a request attribute to all incoming requests.</jc>
+	 * 		<ja>@Override</ja>
+	 * 		<jk>public void</jk> onStartCall(HttpServletRequest req, HttpServletResponse res) {
+	 * 			req.setAttribute(<js>"foobar"</js>, <jk>new</jk> FooBar());
+	 * 		}
+	 * 	}
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple START_CALL methods can be defined on a class.
+	 * 		<br>START_CALL methods on parent classes are invoked before START_CALL methods on child classes.
+	 * 		<br>The order of START_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception.
+	 * 		<br>{@link HttpException HttpExceptions} can be thrown to cause a particular HTTP error status code.
+	 * 		<br>All other exceptions cause an HTTP 500 error status code.
+	 * </ul>
+	 *
+	 * @param req The HTTP servlet request object.
+	 * @param res The HTTP servlet response object.
+	 * @throws Exception Any exception.
+	 */
+	@RestHook(START_CALL)
+	public void onStartCall(HttpServletRequest req, HttpServletResponse res) throws Exception {}
+
+	/**
+	 * Method that gets called immediately before the <ja>@RestMethod</ja> annotated method gets called.
+	 *
+	 * <p>
+	 * At this point, the {@link RestRequest} object has been fully initialized, and all {@link RestGuard} and
+	 * {@link RestMatcher} objects have been called.
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple PRE_CALL methods can be defined on a class.
+	 * 		<br>PRE_CALL methods on parent classes are invoked before PRE_CALL methods on child classes.
+	 * 		<br>The order of PRE_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception.
+	 * 		<br>{@link HttpException HttpExceptions} can be thrown to cause a particular HTTP error status code.
+	 * 		<br>All other exceptions cause an HTTP 500 error status code.
+	 * 	<li>
+	 * 		It's advisable not to mess around with the HTTP body itself since you may end up consuming the body
+	 * 		before the actual REST method has a chance to use it.
+	 * </ul>
+	 *
+	 * @param req The request object.
+	 * @param res The response object.
+	 * @throws Exception Any exception.
+	 */
+	@RestHook(PRE_CALL)
+	public void onPreCall(RestRequest req, RestResponse res) throws Exception {}
+
+	/**
+	 * Method that gets called immediately after the <ja>@RestMethod</ja> annotated method gets called.
+	 *
+	 * <p>
+	 * At this point, the output object returned by the method call has been set on the response, but
+	 * {@link RestConverter RestConverters} have not yet been executed and the response has not yet been written.
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple POST_CALL methods can be defined on a class.
+	 * 		<br>POST_CALL methods on parent classes are invoked before POST_CALL methods on child classes.
+	 * 		<br>The order of POST_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception, although at this point it is too late to set an HTTP error status code.
+	 * </ul>
+	 *
+	 * @param req The request object.
+	 * @param res The response object.
+	 * @throws Exception Any exception.
+	 */
+	@RestHook(POST_CALL)
+	public void onPostCall(RestRequest req, RestResponse res) throws Exception {}
+
+	/**
+	 * Method that gets called right before we exit the servlet service method.
+	 *
+	 * <p>
+	 * At this point, the output has been written and flushed.
+	 *
+	 * <p>
+	 * The following attributes are set on the {@link HttpServletRequest} object that can be useful for logging purposes:
+	 * <ul>
+	 * 	<li><js>"Exception"</js> - Any exceptions thrown during the request.
+	 * 	<li><js>"ExecTime"</js> - Execution time of the request.
+	 * </ul>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default implementation of this method is a no-op.
+	 * 	<li>
+	 * 		Multiple END_CALL methods can be defined on a class.
+	 * 		<br>END_CALL methods on parent classes are invoked before END_CALL methods on child classes.
+	 * 		<br>The order of END_CALL method invocations within a class is alphabetical, then by parameter count, then by parameter types.
+	 * 	<li>
+	 * 		The method can throw any exception, although at this point it is too late to set an HTTP error status code.
+	 * 	<li>
+	 * 		Note that if you override a parent method, you probably need to call <code><jk>super</jk>.parentMethod(...)</code>.
+	 * 		<br>The method is still considered part of the parent class for ordering purposes even though it's
+	 * 		overridden by the child class.
+	 * </ul>
+	 *
+	 * @param req The HTTP servlet request object.
+	 * @param res The HTTP servlet response object.
+	 * @throws Exception Any exception.
+	 */
+	@RestHook(END_CALL)
+	public void onEndCall(HttpServletRequest req, HttpServletResponse res) throws Exception {}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Other methods.
 	//-----------------------------------------------------------------------------------------------------------------
 
 	/**
 	 * Returns the current HTTP request.
 	 *
-	 * @return The current HTTP request.
+	 * @return The current HTTP request, or <jk>null</jk> if it wasn't created.
 	 */
 	public synchronized RestRequest getRequest() {
 		return getContext().getRequest();
@@ -184,9 +476,110 @@ public abstract class BasicRest implements BasicRestConfig {
 	/**
 	 * Returns the current HTTP response.
 	 *
-	 * @return The current HTTP response
+	 * @return The current HTTP response, or <jk>null</jk> if it wasn't created.
 	 */
 	public synchronized RestResponse getResponse() {
 		return getContext().getResponse();
 	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// RestCallHandler
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@Override /* RestCallHandler */
+	public void execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+		callHandler.execute(req, res);
+	}
+
+	@Override /* RestCallHandler */
+	public RestCall createCall(HttpServletRequest req, HttpServletResponse res) {
+		return callHandler.createCall(req, res);
+	}
+
+	@Override /* RestCallHandler */
+	public RestRequest createRequest(RestCall call) throws ServletException {
+		return callHandler.createRequest(call);
+	}
+
+	@Override /* RestCallHandler */
+	public RestResponse createResponse(RestCall call) throws ServletException {
+		return callHandler.createResponse(call);
+	}
+
+	@Override /* RestCallHandler */
+	public void handleResponse(RestCall call) throws Exception {
+		callHandler.handleResponse(call);
+	}
+
+	@Override /* RestCallHandler */
+	public void handleNotFound(RestCall call) throws Exception {
+		callHandler.handleNotFound(call);
+	}
+
+	@Override /* RestCallHandler */
+	public void handleError(RestCall call, Throwable e) throws Exception {
+		callHandler.handleError(call, e);
+	}
+
+	@Override /* RestCallHandler */
+	public Throwable convertThrowable(Throwable t) {
+		return callHandler.convertThrowable(t);
+	}
+
+	@Override /* RestCallHandler */
+	public Map<String,Object> getSessionObjects(RestRequest req, RestResponse res) {
+		return callHandler.getSessionObjects(req, res);
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// RestInfoProvider
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@Override /* RestInfoProvider */
+	public Swagger getSwagger(RestRequest req) throws Exception {
+		return infoProvider.getSwagger(req);
+	}
+
+	@Override /* RestInfoProvider */
+	public String getSiteName(RestRequest req) throws Exception {
+		return infoProvider.getSiteName(req);
+	}
+
+	@Override /* RestInfoProvider */
+	public String getTitle(RestRequest req) throws Exception {
+		return infoProvider.getTitle(req);
+	}
+
+	@Override /* RestInfoProvider */
+	public String getDescription(RestRequest req) throws Exception {
+		return infoProvider.getDescription(req);
+	}
+
+	@Override /* RestInfoProvider */
+	public String getMethodSummary(Method method, RestRequest req) throws Exception {
+		return infoProvider.getMethodSummary(method, req);
+	}
+
+	@Override /* RestInfoProvider */
+	public String getMethodDescription(Method method, RestRequest req) throws Exception {
+		return infoProvider.getMethodDescription(method, req);
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// RestCallLogger
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@Override /* RestCallLogger */
+	public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) {
+		callLogger.log(config, req, res);
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// ClasspathResourceFinder
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@Override /* ClasspathResourceFinder */
+	public InputStream findResource(Class<?> baseClass, String name, Locale locale) throws IOException {
+		return resourceFinder.findResource(baseClass, name, locale);
+	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
index 9a4be75..3c5a964 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
@@ -136,7 +136,8 @@ public class BasicRestCallHandler implements RestCallHandler {
 				}
 			}
 
-			call.debug(isDebug(call));
+			if (isDebug(call))
+				call.debug(true);
 
 			context.startCall(call);
 
@@ -209,12 +210,19 @@ public class BasicRestCallHandler implements RestCallHandler {
 	}
 
 	private boolean isDebug(RestCall call) {
-		Enablement e = context.getDebug();
+		Enablement e = null;
+		RestMethodContext mc = call.getRestMethodContext();
+		if (mc != null)
+			e = mc.getDebug();
+		if (e == null)
+			e = context.getDebug();
 		if (e == TRUE)
 			return true;
 		if (e == FALSE)
 			return false;
-		return "true".equalsIgnoreCase(call.getRequest().getHeader("X-Debug"));
+		if (e == PER_REQUEST)
+			return "true".equalsIgnoreCase(call.getRequest().getHeader("X-Debug"));
+		return false;
 	}
 
 	/**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
index 21b6121..483e6ae 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallLogger.java
@@ -50,9 +50,9 @@ public class BasicRestCallLogger implements RestCallLogger {
 	 */
 	public BasicRestCallLogger(RestContext context) {
 		this.context = context;
-		this.loggerName = context.getResource().getClass().getName();
+		this.loggerName = context == null ? getClass().getName() : context.getResource().getClass().getName();
 		this.logger = Logger.getLogger(getLoggerName());
-		this.stackTraceDb = context.getStackTraceDb();
+		this.stackTraceDb = context == null ? null : context.getStackTraceDb();
 	}
 
 	/**
@@ -102,7 +102,8 @@ public class BasicRestCallLogger implements RestCallLogger {
 	 * @return This object (for method chaining).
 	 */
 	public BasicRestCallLogger resetStackTraces() {
-		stackTraceDb.reset();
+		if (stackTraceDb != null)
+			stackTraceDb.reset();
 		return this;
 	}
 
@@ -229,7 +230,21 @@ public class BasicRestCallLogger implements RestCallLogger {
 			sb.append("\n=== END ======================================================================");
 		}
 
-		getLogger().log(level, sb.toString(), e);
+		log(level, sb.toString(), e);
+	}
+
+	/**
+	 * Logs the specified message to the logger.
+	 *
+	 * <p>
+	 * Subclasses can override this method to capture messages being sent to the logger.
+	 *
+	 * @param level The log level.
+	 * @param msg The log message.
+	 * @param e The exception.
+	 */
+	protected void log(Level level, String msg, Throwable e) {
+		getLogger().log(level, msg, e);
 	}
 
 	private byte[] getRequestBody(HttpServletRequest req) {
@@ -245,7 +260,7 @@ public class BasicRestCallLogger implements RestCallLogger {
 	}
 
 	private StackTraceInfo getStackTraceInfo(RestCallLoggerConfig config, Throwable e) {
-		if (e == null || ! config.isUseStackTraceHashing())
+		if (e == null || stackTraceDb == null || ! config.isUseStackTraceHashing())
 			return null;
 		stackTraceDb.add(e);
 		return stackTraceDb.getStackTraceInfo(e);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
index 3611372..6f9f5c0 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
@@ -12,15 +12,10 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest;
 
-import static org.apache.juneau.http.HttpMethodName.*;
-
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.dto.swagger.*;
-import org.apache.juneau.dto.swagger.ui.*;
 import org.apache.juneau.html.*;
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.json.*;
-import org.apache.juneau.jsonschema.annotation.*;
 import org.apache.juneau.msgpack.*;
 import org.apache.juneau.oapi.*;
 import org.apache.juneau.plaintext.*;
@@ -103,9 +98,7 @@ import org.apache.juneau.xmlschema.XmlSchemaDocSerializer;
 
 	// Basic page navigation links.
 	navlinks={
-		"up: request:/..",
-		"options: servlet:/?method=OPTIONS",
-		"stats: servlet:/stats"
+		"up: request:/.."
 	},
 
 	// Default stylesheet to use for the page.
@@ -124,92 +117,9 @@ import org.apache.juneau.xmlschema.XmlSchemaDocSerializer;
 	// By default, table cell contents should not wrap.
 	nowrap="true"
 )
-@JsonSchemaConfig(
-	// Add descriptions to the following types when not specified:
-	addDescriptionsTo="bean,collection,array,map,enum",
-	// Add x-example to the following types:
-	addExamplesTo="bean,collection,array,map",
-	// Don't generate schema information on the Swagger bean itself or HTML beans.
-	ignoreTypes="Swagger,org.apache.juneau.dto.html5.*",
-	// Use $ref references for bean definitions to reduce duplication in Swagger.
-	useBeanDefs="true"
-)
 @BeanConfig(
 	// When parsing generated beans, ignore unknown properties that may only exist as getters and not setters.
-	ignoreUnknownBeanProperties="true",
-	// POJO swaps to apply to all serializers/parsers on this method.
-	pojoSwaps={
-		// Use the SwaggerUI swap when rendering Swagger beans.
-		// This is a per-media-type swap that only applies to text/html requests.
-		SwaggerUI.class
-	}
+	ignoreUnknownBeanProperties="true"
 )
 @SuppressWarnings("deprecation")
-public interface BasicRestConfig {
-
-	/**
-	 * [OPTIONS /*] - Show resource options.
-	 *
-	 * @param req The HTTP request.
-	 * @return A bean containing the contents for the OPTIONS page.
-	 */
-	@RestMethod(name=OPTIONS, path="/*",
-		summary="Swagger documentation",
-		description="Swagger documentation for this resource."
-	)
-	@HtmlDocConfig(
-		// Should override config annotations defined on class.
-		rank=10,
-		// Override the nav links for the swagger page.
-		navlinks={
-			"back: servlet:/",
-			"json: servlet:/?method=OPTIONS&Accept=text/json&plainText=true"
-		},
-		// Never show aside contents of page inherited from class.
-		aside="NONE"
-	)
-	public Swagger getOptions(RestRequest req);
-
-	/**
-	 * [* /error] - Error occurred.
-	 *
-	 * <p>
-	 * Servlet chains will often automatically redirect to <js>"/error"</js> when any sort of error condition occurs
-	 * (such as failed authentication) and will set appropriate response parameters (such as an <c>WWW-Authenticate</c>
-	 * response header).
-	 *
-	 * <p>
-	 * These responses should be left as-is without any additional processing.
-	 */
-	@RestMethod(name=ANY, path="/error",
-		summary="Error occurred",
-		description="An error occurred during handling of the request."
-	)
-	public void error();
-
-	/**
-	 * [GET /stats] - Timing statistics.
-	 *
-	 * <p>
-	 * Timing statistics for method invocations on this resource.
-	 *
-	 * @param req The HTTP request.
-	 * @return A collection of timing statistics for each annotated method on this resource.
-	 */
-	@RestMethod(name=GET, path="/stats",
-		summary="Timing statistics",
-		description="Timing statistics for method invocations on this resource."
-	)
-	@HtmlDocConfig(
-		// Should override config annotations defined on class.
-		rank=10,
-		// Override the nav links for the swagger page.
-		navlinks={
-			"back: servlet:/",
-			"json: servlet:/stats?Accept=text/json&plainText=true"
-		},
-		// Never show aside contents of page inherited from class.
-		aside="NONE"
-	)
-	public RestContextStats getStats(RestRequest req);
-}
+public interface BasicRestConfig {}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestMethods.java
similarity index 59%
copy from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
copy to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestMethods.java
index 3611372..02d0edf 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestMethods.java
@@ -17,112 +17,22 @@ import static org.apache.juneau.http.HttpMethodName.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.dto.swagger.*;
 import org.apache.juneau.dto.swagger.ui.*;
-import org.apache.juneau.html.*;
 import org.apache.juneau.html.annotation.*;
-import org.apache.juneau.json.*;
 import org.apache.juneau.jsonschema.annotation.*;
-import org.apache.juneau.msgpack.*;
-import org.apache.juneau.oapi.*;
-import org.apache.juneau.plaintext.*;
 import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.serializer.annotation.*;
-import org.apache.juneau.soap.*;
-import org.apache.juneau.uon.*;
-import org.apache.juneau.urlencoding.*;
-import org.apache.juneau.xml.*;
-import org.apache.juneau.xmlschema.XmlSchemaDocSerializer;
 
 /**
- * Basic configuration for a REST resource.
- *
- * <p>
- * Classes that don't extend from {@link BasicRestServlet} can implement this interface to
- * be configured with the same serializers/parsers/etc... as {@link BasicRestServlet}.
+ * TODO
  */
-@Rest(
-
-	// Default serializers for all Java methods in the class.
-	serializers={
-		HtmlDocSerializer.class, // HTML must be listed first because Internet Explore does not include text/html in their Accept header.
-		HtmlStrippedDocSerializer.class,
-		HtmlSchemaDocSerializer.class,
-		JsonSerializer.class,
-		SimpleJsonSerializer.class,
-		JsonSchemaSerializer.class,
-		XmlDocSerializer.class,
-		XmlSchemaDocSerializer.class,
-		UonSerializer.class,
-		UrlEncodingSerializer.class,
-		OpenApiSerializer.class,
-		MsgPackSerializer.class,
-		SoapXmlSerializer.class,
-		PlainTextSerializer.class
-	},
-
-	// Default parsers for all Java methods in the class.
-	parsers={
-		JsonParser.class,
-		JsonParser.Simple.class,
-		XmlParser.class,
-		HtmlParser.class,
-		UonParser.class,
-		UrlEncodingParser.class,
-		OpenApiParser.class,
-		MsgPackParser.class,
-		PlainTextParser.class
-	},
-
-	// Optional external configuration file.
-	config="$S{juneau.configFile,SYSTEM_DEFAULT}",
-
-	// These are static files that are served up by the servlet under the specified sub-paths.
-	// For example, "/servletPath/htdocs/javadoc.css" resolves to the file "[servlet-package]/htdocs/javadoc.css"
-	// By default, we define static files through the external configuration file.
-	staticFiles="$C{REST/staticFiles,htdocs:/htdocs,htdocs:htdocs}",
-
-	logging=@Logging(
-		level="INFO",
-		useStackTraceHashing="true",
-		rules={
-			@LoggingRule(codes="500-", level="WARNING")
-		}
-	)
-)
-@SerializerConfig(
-	// Enable automatic resolution of URI objects to root-relative values.
-	uriResolution="ROOT_RELATIVE"
-)
 @HtmlDocConfig(
 
-	// Default page header contents.
-	header={
-		"<h1>$R{resourceTitle}</h1>",  // Use @Rest(title)
-		"<h2>$R{methodSummary,resourceDescription}</h2>", // Use either @RestMethod(summary) or @Rest(description)
-		"$C{REST/header}"  // Extra header HTML defined in external config file.
-	},
-
 	// Basic page navigation links.
 	navlinks={
 		"up: request:/..",
 		"options: servlet:/?method=OPTIONS",
 		"stats: servlet:/stats"
-	},
-
-	// Default stylesheet to use for the page.
-	// Can be overridden from external config file.
-	// Default is DevOps look-and-feel (aka Depression look-and-feel).
-	stylesheet="$C{REST/theme,servlet:/htdocs/themes/devops.css}",
-
-	// Default contents to add to the <head> section of the HTML page.
-	// Use it to add a favicon link to the page.
-	head="$C{REST/head}",
-
-	// No default page footer contents.
-	// Can be overridden from external config file.
-	footer="$C{REST/footer}",
+	}
 
-	// By default, table cell contents should not wrap.
-	nowrap="true"
 )
 @JsonSchemaConfig(
 	// Add descriptions to the following types when not specified:
@@ -135,8 +45,6 @@ import org.apache.juneau.xmlschema.XmlSchemaDocSerializer;
 	useBeanDefs="true"
 )
 @BeanConfig(
-	// When parsing generated beans, ignore unknown properties that may only exist as getters and not setters.
-	ignoreUnknownBeanProperties="true",
 	// POJO swaps to apply to all serializers/parsers on this method.
 	pojoSwaps={
 		// Use the SwaggerUI swap when rendering Swagger beans.
@@ -144,8 +52,7 @@ import org.apache.juneau.xmlschema.XmlSchemaDocSerializer;
 		SwaggerUI.class
 	}
 )
-@SuppressWarnings("deprecation")
-public interface BasicRestConfig {
+public interface BasicRestMethods {
 
 	/**
 	 * [OPTIONS /*] - Show resource options.
@@ -171,6 +78,29 @@ public interface BasicRestConfig {
 	public Swagger getOptions(RestRequest req);
 
 	/**
+	 * [GET /options] - Show resource options.
+	 *
+	 * @param req The HTTP request.
+	 * @return A bean containing the contents for the OPTIONS page.
+	 */
+	@RestMethod(name=OPTIONS, path="/*",
+		summary="Swagger documentation",
+		description="Swagger documentation for this resource."
+	)
+	@HtmlDocConfig(
+		// Should override config annotations defined on class.
+		rank=10,
+		// Override the nav links for the swagger page.
+		navlinks={
+			"back: servlet:/",
+			"json: servlet:/?Accept=text/json&plainText=true"
+		},
+		// Never show aside contents of page inherited from class.
+		aside="NONE"
+	)
+	public Swagger getOptions2(RestRequest req);
+
+	/**
 	 * [* /error] - Error occurred.
 	 *
 	 * <p>
@@ -212,4 +142,5 @@ public interface BasicRestConfig {
 		aside="NONE"
 	)
 	public RestContextStats getStats(RestRequest req);
+
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java
index 9b60cad..11fb1ba 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestServlet.java
@@ -164,7 +164,7 @@ import org.apache.juneau.xml.*;
 		"stats: servlet:/stats"
 	}
 )
-public abstract class BasicRestServlet extends RestServlet implements BasicRestConfig {
+public abstract class BasicRestServlet extends RestServlet implements BasicRestConfig, BasicRestMethods {
 	private static final long serialVersionUID = 1L;
 
 	/**
@@ -180,6 +180,18 @@ public abstract class BasicRestServlet extends RestServlet implements BasicRestC
 	}
 
 	/**
+	 * [GET /options] - Show resource options.
+	 *
+	 * @param req The HTTP request.
+	 * @return A bean containing the contents for the OPTIONS page.
+	 */
+	@Override /* BasicRestConfig */
+	public Swagger getOptions2(RestRequest req) {
+		// Localized Swagger for this resource is available through the RestRequest object.
+		return req.getSwagger();
+	}
+
+	/**
 	 * [* /error] - Error occurred.
 	 *
 	 * <p>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java
index beca659..53dfd76 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java
@@ -48,5 +48,18 @@ public enum Enablement {
 		}
 		return null;
 	}
+
+	/**
+	 * Returns <jk>true</jk> if this enum is one of the specified values.
+	 *
+	 * @param values The values to check against.
+	 * @return <jk>true</jk> if this enum is one of the specified values.
+	 */
+	public boolean isOneOf(Enablement...values) {
+		for (Enablement v : values)
+			 if (this == v)
+				 return true;
+		return false;
+	}
 }
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/NoOpRestCallLogger.java
similarity index 71%
copy from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java
copy to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/NoOpRestCallLogger.java
index beca659..9410447 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/Enablement.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/NoOpRestCallLogger.java
@@ -12,41 +12,15 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest;
 
-import org.apache.juneau.internal.*;
+import javax.servlet.http.*;
 
 /**
- * Represents the enablement settings of a feature.
+ * Can be used to force call logging to be disabled.
  */
-public enum Enablement {
+public class NoOpRestCallLogger implements RestCallLogger {
 
-	/**
-	 * Feature is always enabled.
-	 */
-	TRUE,
-
-	/**
-	 * Feature is enabled per HTTP request.
-	 */
-	PER_REQUEST,
-
-	/**
-	 * Feature is disabled.
-	 */
-	FALSE;
-
-	/**
-	 * Retrieves this enum using case-insensitive matching.
-	 *
-	 * @param s The enum name to resolve.
-	 * @return The resolved value.
-	 */
-	public static Enablement fromString(String s) {
-		if (! StringUtils.isEmpty(s)) {
-			try {
-				return valueOf(s.replace('-', '_').toUpperCase());
-			} catch (IllegalArgumentException  e) {}
-		}
-		return null;
+	@Override
+	public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) {
+		// Do nothing.
 	}
 }
-
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
index e69e5b4..f897a42 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.rest;
 
 import java.io.*;
+import java.lang.reflect.*;
 import java.util.*;
 
 import javax.servlet.http.*;
@@ -76,10 +77,10 @@ public class RestCall {
 
 	/**
 	 * Sets the method context on this call.
-	 * 
-	 * Used for logging statistics on the method. 
-	 * 
-	 * @param value The new value. 
+	 *
+	 * Used for logging statistics on the method.
+	 *
+	 * @param value The new value.
 	 * @return This object (for method chaining).
 	 */
 	public RestCall restMethodContext(RestMethodContext value) {
@@ -150,13 +151,22 @@ public class RestCall {
 
 	/**
 	 * Returns the method context of this call.
-	 * 
+	 *
 	 * @return The method context of this call.
 	 */
 	public RestMethodContext getRestMethodContext() {
 		return rmethod;
 	}
 
+	/**
+	 * Returns the Java method of this call.
+	 *
+	 * @return The java method of this call, or <jk>null</jk> if it hasn't been determined yet.
+	 */
+	public Method getJavaMethod() {
+		return rmethod == null ? null : rmethod.method;
+	}
+
 	//------------------------------------------------------------------------------------------------------------------
 	// Setters.
 	//------------------------------------------------------------------------------------------------------------------
@@ -194,8 +204,10 @@ public class RestCall {
 		if (b) {
 			req = CachingHttpServletRequest.wrap(req);
 			res = CachingHttpServletResponse.wrap(res);
+			req.setAttribute("Debug", true);
+		} else {
+			req.removeAttribute("Debug");
 		}
-		req.setAttribute("Debug", b);
 		return this;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallLoggerConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallLoggerConfig.java
index 4c925d0..a5e326b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallLoggerConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallLoggerConfig.java
@@ -30,7 +30,7 @@ public class RestCallLoggerConfig {
 	/**
 	 * Default empty logging config.
 	 */
-	public static final RestCallLoggerConfig DEFAULT = RestCallLoggerConfig.create().build();
+	public static final RestCallLoggerConfig DEFAULT_NOOP = RestCallLoggerConfig.create().build();
 
 	/**
 	 * Default debug logging config.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index ccda33e..879ba87 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -464,10 +464,21 @@ public final class RestContext extends BeanContext {
 	 *
 	 * <ul class='notes'>
 	 * 	<li>
+	 * 		The default call handler if not specified is {@link BasicRestCallHandler}.
+	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link RestCallHandler} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
+	 * 		The {@link RestServlet} class itself implements the {@link RestCallHandler} interface with the same
+	 * 		functionality as {@link BasicRestCallHandler} that gets used if not overridden by this annotation.
+	 * 		<br>Subclasses can also alter the behavior by overriding these methods.
+	 * 	<li>
 	 * 		When defined as a class, the implementation must have one of the following constructors:
 	 * 		<ul>
 	 * 			<li><code><jk>public</jk> T(RestContext)</code>
 	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
 	 * 		</ul>
 	 * 	<li>
 	 * 		Inner classes of the REST resource class are allowed.
@@ -537,6 +548,28 @@ public final class RestContext extends BeanContext {
 	 * 	}
 	 * </p>
 	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		The default call logger if not specified is {@link BasicRestCallLogger}.
+	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link RestCallLogger} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
+	 * 		The {@link RestServlet} class itself implements the {@link RestCallLogger} interface with the same
+	 * 		functionality as {@link BasicRestCallLogger} that gets used if not overridden by this annotation.
+	 * 		<br>Subclasses can also alter the behavior by overriding this method.
+	 * 	<li>
+	 * 		When defined as a class, the implementation must have one of the following constructors:
+	 * 		<ul>
+	 * 			<li><code><jk>public</jk> T(RestContext)</code>
+	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
+	 * 		</ul>
+	 * 	<li>
+	 * 		Inner classes of the REST resource class are allowed.
+	 * </ul>
+	 *
 	 * <ul class='seealso'>
 	 * 	<li class='link'>{@doc juneau-rest-server.LoggingAndDebugging}
 	 * </ul>
@@ -551,7 +584,7 @@ public final class RestContext extends BeanContext {
 	 * 	<li><b>ID:</b>  {@link org.apache.juneau.rest.RestContext#REST_callLoggerConfig REST_callLoggerConfig}
 	 * 	<li><b>Name:</b>  <js>"RestContext.callLoggerConfig.o"</js>
 	 * 	<li><b>Data type:</b>  {@link org.apache.juneau.rest.RestCallLoggerConfig}
-	 * 	<li><b>Default:</b>  {@link org.apache.juneau.rest.RestCallLoggerConfig#DEFAULT}
+	 * 	<li><b>Default:</b>  {@link org.apache.juneau.rest.RestCallLoggerConfig#DEFAULT_NOOP}
 	 * 	<li><b>Session property:</b>  <jk>false</jk>
 	 * 	<li><b>Annotations:</b>
 	 * 		<ul>
@@ -1033,11 +1066,47 @@ public final class RestContext extends BeanContext {
 	 * <ul class='spaced-list'>
 	 * 	<li>
 	 * 		HTTP request/response bodies are cached in memory for logging purposes.
+	 * 	<li>
+	 * 		Request/response messages are automatically logged always or per request.
 	 * </ul>
 	 */
 	public static final String REST_debug = PREFIX + ".debug.s";
 
 	/**
+	 * Configuration property:  Debug mode on specified classes/methods.
+	 *
+	 * <h5 class='section'>Property:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li><b>ID:</b>  {@link org.apache.juneau.rest.RestContext#REST_debugOn REST_debugOn}
+	 * 	<li><b>Name:</b>  <js>"RestContext.debugOn.s"</js>
+	 * 	<li><b>Data type:</b>  <c>String</c> (comma-delimited)
+	 * 	<li><b>System property:</b>  <c>RestContext.debugOn</c>
+	 * 	<li><b>Environment variable:</b>  <c>RESTCONTEXT_DEBUGON</c>
+	 * 	<li><b>Default:</b>  Empty string
+	 * 	<li><b>Session property:</b>  <jk>false</jk>
+	 * 	<li><b>Annotations:</b>
+	 * 		<ul>
+	 * 			<li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#debugOn()}
+	 * 		</ul>
+	 * 	<li><b>Methods:</b>
+	 * 		<ul>
+	 * 			<li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#debugOn(String)}
+	 * 		</ul>
+	 * </ul>
+	 *
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * Enables the following:
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		HTTP request/response bodies are cached in memory for logging purposes.
+	 * 	<li>
+	 * 		Request/response messages are automatically logged always or per request.
+	 * </ul>
+	 */
+	public static final String REST_debugOn = PREFIX + ".debugOn.s";
+
+	/**
 	 * Configuration property:  Default character encoding.
 	 *
 	 * <h5 class='section'>Property:</h5>
@@ -1362,10 +1431,21 @@ public final class RestContext extends BeanContext {
 	 *
 	 * <ul class='notes'>
 	 * 	<li>
+	 * 		The default info provider if not specified is {@link BasicRestInfoProvider}.
+	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link RestInfoProvider} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
+	 * 		The {@link RestServlet} class itself implements the {@link RestInfoProvider} interface with the same
+	 * 		functionality as {@link BasicRestInfoProvider} that gets used if not overridden by this annotation.
+	 * 		<br>Subclasses can also alter the behavior by overriding these methods.
+	 * 	<li>
 	 * 		When defined as a class, the implementation must have one of the following constructors:
 	 * 		<ul>
 	 * 			<li><code><jk>public</jk> T(RestContext)</code>
 	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
 	 * 		</ul>
 	 * 	<li>
 	 * 		Inner classes of the REST resource class are allowed.
@@ -2362,6 +2442,8 @@ public final class RestContext extends BeanContext {
 	 * 		<ul>
 	 * 			<li><code><jk>public</jk> T(RestContext)</code>
 	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
 	 * 		</ul>
 	 * 	<li>
 	 * 		Inner classes of the REST resource class are allowed.
@@ -3547,6 +3629,8 @@ public final class RestContext extends BeanContext {
 	private final ThreadLocal<RestRequest> req = new ThreadLocal<>();
 	private final ThreadLocal<RestResponse> res = new ThreadLocal<>();
 
+	private final ReflectionMap<Enablement> debugEnablement;
+
 	/**
 	 * Constructor.
 	 *
@@ -3634,9 +3718,32 @@ public final class RestContext extends BeanContext {
 			allowedMethodHeaders = newUnmodifiableSortedCaseInsensitiveSet(getStringPropertyWithNone(REST_allowedMethodHeaders, ""));
 			renderResponseStackTraces = getBooleanProperty(REST_renderResponseStackTraces, false);
 			useStackTraceHashes = getBooleanProperty(REST_useStackTraceHashes, true);
-			debug = getInstanceProperty(REST_debug, Enablement.class, Enablement.FALSE);
 			clientVersionHeader = getStringProperty(REST_clientVersionHeader, "X-Client-Version");
 
+			ReflectionMap.Builder<Enablement> deb = ReflectionMap.create(Enablement.class);
+			for (String s : split(getStringProperty(REST_debugOn, ""))) {
+				s = s.trim();
+				if (! s.isEmpty()) {
+					int i = s.indexOf('=');
+					if (i == -1)
+						deb.append(s.trim(), Enablement.TRUE);
+					else
+						deb.append(s.substring(0, i).trim(), Enablement.fromString(s.substring(i+1).trim()));
+				}
+			}
+
+			Enablement de = getInstanceProperty(REST_debug, Enablement.class, Enablement.FALSE);
+			if (de != null)
+				deb.append(rci.getFullName(), de);
+			for (MethodInfo mi : rci.getPublicMethods())
+				for (RestMethod a : mi.getAnnotations(RestMethod.class))
+					if (a != null && ! a.debug().isEmpty())
+						deb.append(mi.getFullName(), Enablement.fromString(a.debug()));
+
+			this.debugEnablement = deb.build();
+
+			this.debug = debugEnablement.find(rci.inner(), Enablement.class).orElse(Enablement.FALSE);
+
 			responseHandlers = getInstanceArrayProperty(REST_responseHandlers, resource, ResponseHandler.class, new ResponseHandler[0], resourceResolver, this);
 
 			Map<Class<?>,RestMethodParam> _paramResolvers = new HashMap<>();
@@ -3654,17 +3761,13 @@ public final class RestContext extends BeanContext {
 
 			logger = getInstanceProperty(REST_logger, resource, RestLogger.class, NoOpRestLogger.class, resourceResolver, this);
 
-			if (debug == Enablement.TRUE) {
-				this.callLoggerConfig = RestCallLoggerConfig.DEFAULT_DEBUG;
-			} else {
-				Object clc = getProperty(REST_callLoggerConfig);
-				if (clc instanceof RestCallLoggerConfig)
-					this.callLoggerConfig = (RestCallLoggerConfig)clc;
-				else if (clc instanceof ObjectMap)
-					this.callLoggerConfig = RestCallLoggerConfig.create().apply((ObjectMap)clc).build();
-				else
-					this.callLoggerConfig = RestCallLoggerConfig.DEFAULT;
-			}
+			Object clc = getProperty(REST_callLoggerConfig);
+			if (clc instanceof RestCallLoggerConfig)
+				this.callLoggerConfig = (RestCallLoggerConfig)clc;
+			else if (clc instanceof ObjectMap)
+				this.callLoggerConfig = RestCallLoggerConfig.create().apply((ObjectMap)clc).build();
+			else
+				this.callLoggerConfig = RestCallLoggerConfig.DEFAULT_NOOP;
 
 			this.stackTraceDb = new StackTraceDatabase(callLoggerConfig.getStackTraceHashingTimeout(), RestMethodContext.class);
 
@@ -4721,7 +4824,7 @@ public final class RestContext extends BeanContext {
 	 * Returns <jk>true</jk> if debug mode is enabled on this resource.
 	 *
 	 * <div class='warn'>
-	 * 	<b>Deprecated</b> - Use {@link #getDebug()}
+	 * 	<b>Deprecated</b> - Use {@link #getDebug(Method)}
 	 * </div>
 	 *
 	 * <ul class='seealso'>
@@ -4737,12 +4840,15 @@ public final class RestContext extends BeanContext {
 	}
 
 	/**
-	 * Returns the debug setting on this context.
+	 * Returns the debug setting on this context for the specified method.
 	 *
-	 * @return The debug setting on this context.
+	 * @param method The java method.
+	 * @return The debug setting on this context or <jk>null</jk> not specified for this method.
 	 */
-	public Enablement getDebug() {
-		return debug;
+	public Enablement getDebug(Method method) {
+		if (method == null)
+			return null;
+		return debugEnablement.find(method).orElse(null);
 	}
 
 	/**
@@ -5317,6 +5423,10 @@ public final class RestContext extends BeanContext {
 		this.res.set(res);
 	}
 
+	Enablement getDebug() {
+		return debug;
+	}
+
 	/**
 	 * Clear any request state information on this context.
 	 * This should always be called in a finally block in the RestServlet.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
index 6415d59..7b676d3 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextBuilder.java
@@ -664,7 +664,7 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 *
 	 * @param value
 	 * 	The new value for this setting.
-	 * 	<br>The default is {@link RestCallLoggerConfig#DEFAULT}.
+	 * 	<br>The default is {@link RestCallLoggerConfig#DEFAULT_NOOP}.
 	 * @return This object (for method chaining).
 	 */
 	@ConfigurationProperty
@@ -868,6 +868,8 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	 * <ul class='spaced-list'>
 	 * 	<li>
 	 * 		HTTP request/response bodies are cached in memory for logging purposes.
+	 * 	<li>
+	 * 		Request/response messages are automatically logged always or per request.
 	 * </ul>
 	 *
 	 * @param value The new value for this setting.
@@ -879,6 +881,29 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	}
 
 	/**
+	 * Configuration property:  Debug mode on specified classes/methods.
+	 *
+	 * Enables the following:
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		HTTP request/response bodies are cached in memory for logging purposes.
+	 * 	<li>
+	 * 		Request/response messages are automatically logged.
+	 * </ul>
+	 *
+	 * <ul class='seealso'>
+	 * 	<li class='jf'>{@link RestContext#REST_debugOn}
+	 * </ul>
+	 *
+	 * @param value The new value for this setting.
+	 * @return This object (for method chaining).
+	 */
+	@ConfigurationProperty
+	public RestContextBuilder debugOn(String value) {
+		return set(REST_debugOn, value);
+	}
+
+	/**
 	 * Configuration property:  Default character encoding.
 	 *
 	 * <p>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
index 67ea0c9..5226e7b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
@@ -12,6 +12,7 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest;
 
+import static org.apache.juneau.rest.Enablement.*;
 import static javax.servlet.http.HttpServletResponse.*;
 import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.CollectionUtils.*;
@@ -750,19 +751,15 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 		this.supportedAcceptTypes = getListProperty(REST_produces, MediaType.class, serializers.getSupportedMediaTypes());
 		this.supportedContentTypes = getListProperty(REST_consumes, MediaType.class, parsers.getSupportedMediaTypes());
 
-		this.debug = getInstanceProperty(RESTMETHOD_debug, Enablement.class, context.getDebug());
+		this.debug = context.getDebug(method);
 
-		if (debug == Enablement.TRUE) {
-			this.callLoggerConfig = RestCallLoggerConfig.DEFAULT_DEBUG;
-		} else {
-			Object clc = getProperty(RESTMETHOD_callLoggerConfig);
-			if (clc instanceof RestCallLoggerConfig)
-				this.callLoggerConfig = (RestCallLoggerConfig)clc;
-			else if (clc instanceof ObjectMap)
-				this.callLoggerConfig = RestCallLoggerConfig.create().parent(context.getCallLoggerConfig()).apply((ObjectMap)clc).build();
-			else
-				this.callLoggerConfig = context.getCallLoggerConfig();
-		}
+		Object clc = getProperty(RESTMETHOD_callLoggerConfig);
+		if (clc instanceof RestCallLoggerConfig)
+			this.callLoggerConfig = (RestCallLoggerConfig)clc;
+		else if (clc instanceof ObjectMap)
+			this.callLoggerConfig = RestCallLoggerConfig.create().parent(context.getCallLoggerConfig()).apply((ObjectMap)clc).build();
+		else
+			this.callLoggerConfig = context.getCallLoggerConfig();
 	}
 
 	ResponseBeanMeta getResponseBeanMeta(Object o) {
@@ -901,7 +898,24 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 
 		context.preCall(req, res);
 
-		call.debug(req.isDebug()).loggerConfig(req.getCallLoggerConfig());
+		call.loggerConfig(callLoggerConfig);
+
+		if (debug == TRUE) {
+			call.debug(true);
+			call.loggerConfig(RestCallLoggerConfig.DEFAULT_DEBUG);
+		} else if (debug == FALSE) {
+			call.debug(false);
+			call.loggerConfig(RestCallLoggerConfig.DEFAULT_NOOP);
+		} else if (debug == PER_REQUEST) {
+			boolean b = "true".equalsIgnoreCase(req.getHeader("X-Debug"));
+			if (b) {
+				call.debug(true);
+				call.loggerConfig(RestCallLoggerConfig.DEFAULT_DEBUG);
+			} else {
+				call.debug(false);
+				call.loggerConfig(RestCallLoggerConfig.DEFAULT_NOOP);
+			}
+		}
 
 		Object[] args = new Object[methodParams.length];
 		for (int i = 0; i < methodParams.length; i++) {
@@ -921,6 +935,17 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 			Object output;
 			try {
 				output = methodInvoker.invoke(context.getResource(), args);
+
+				// Handle manual call to req.setDebug().
+				Boolean debug = ObjectUtils.castOrNull(req.getAttribute("Debug"), Boolean.class);
+				if (debug == Boolean.TRUE) {
+					call.debug(true);
+					call.loggerConfig(RestCallLoggerConfig.DEFAULT_DEBUG);
+				} else if (debug == Boolean.FALSE) {
+					call.debug(false);
+					call.loggerConfig(RestCallLoggerConfig.DEFAULT_NOOP);
+				}
+
 				if (res.getStatus() == 0)
 					res.setStatus(200);
 				if (! method.getReturnType().equals(Void.TYPE)) {
@@ -956,26 +981,26 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 		}
 		return SC_OK;
 	}
-
-	protected void addStatusCode(int code) {
-		AtomicInteger ai = statusCodes.get(code);
-		if (ai == null) {
-			synchronized(statusCodes) {
-				ai = new AtomicInteger();
-				statusCodes.putIfAbsent(code, ai);
-				ai = statusCodes.get(code);
-			}
-		}
-		ai.incrementAndGet();
-	}
-
-	protected Map<Integer,Integer> getStatusCodes() {
-		Map<Integer,Integer> m = new TreeMap<>();
-		for (Map.Entry<Integer,AtomicInteger> e : statusCodes.entrySet())
-			m.put(e.getKey(), e.getValue().get());
-		return m;
-	}
-
+//
+//	protected void addStatusCode(int code) {
+//		AtomicInteger ai = statusCodes.get(code);
+//		if (ai == null) {
+//			synchronized(statusCodes) {
+//				ai = new AtomicInteger();
+//				statusCodes.putIfAbsent(code, ai);
+//				ai = statusCodes.get(code);
+//			}
+//		}
+//		ai.incrementAndGet();
+//	}
+//
+//	protected Map<Integer,Integer> getStatusCodes() {
+//		Map<Integer,Integer> m = new TreeMap<>();
+//		for (Map.Entry<Integer,AtomicInteger> e : statusCodes.entrySet())
+//			m.put(e.getKey(), e.getValue().get());
+//		return m;
+//	}
+//
 	/*
 	 * compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list.
 	 * It maintains the order in which matches are made during requests.
@@ -1061,21 +1086,16 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	}
 
 	/**
-	 * Returns whether debug is enabled on this method.
-	 *
-	 * @return <jk>true</jk> if debug is enabled on this method.
-	 */
-	protected Enablement getDebug() {
-		return debug;
-	}
-
-	/**
 	 * @return The REST call logger config for this method.
 	 */
 	protected RestCallLoggerConfig getCallLoggerConfig() {
 		return callLoggerConfig;
 	}
 
+	Enablement getDebug() {
+		return debug;
+	}
+
 	@Override /* Object */
 	public boolean equals(Object o) {
 		if (! (o instanceof RestMethodContext))
@@ -1119,4 +1139,4 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 				.append("priority", priority)
 			);
 	}
-}
\ No newline at end of file
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 3310a30..ba287a0 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -209,7 +209,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 			.maxInput(rjm.maxInput);
 
 		if (isDebug()) {
-			setDebug();
+			inner = CachingHttpServletRequest.wrap(inner);
 		}
 
 		String stylesheet = getQuery().getString("stylesheet");
@@ -1314,14 +1314,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	 */
 	public boolean isDebug() {
 		Boolean b = ObjectUtils.castOrNull(getAttribute("Debug"), Boolean.class);
-		if (b != null)
-			return b;
-		Enablement e = restJavaMethod != null ? restJavaMethod.getDebug() : context.getDebug();
-		if (e == TRUE)
-			return true;
-		if (e == FALSE)
-			return false;
-		return "true".equalsIgnoreCase(getHeader("X-Debug"));
+		return b == null ? false : b;
 	}
 
 	/**
@@ -1802,7 +1795,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	public RestCallLoggerConfig getCallLoggerConfig() {
 		if (restJavaMethod != null)
 			return restJavaMethod.getCallLoggerConfig();
-		return RestCallLoggerConfig.DEFAULT;
+		return RestCallLoggerConfig.DEFAULT_NOOP;
 	}
 
 	void close() {
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
index f1eee7d..58e14c8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/StatusStats.java
@@ -21,6 +21,7 @@ import org.apache.juneau.annotation.*;
  *
  */
 @Bean(bpi="resource,methods")
+@SuppressWarnings("javadoc")
 public class StatusStats implements Comparable<StatusStats> {
 
 	private final Class<?> resource;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index 43d1149..fd03bff 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -169,9 +169,22 @@ public @interface Rest {
 	 * 	<li>
 	 * 		The default call handler if not specified is {@link BasicRestCallHandler}.
 	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link RestCallHandler} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
 	 * 		The {@link RestServlet} class itself implements the {@link RestCallHandler} interface with the same
 	 * 		functionality as {@link BasicRestCallHandler} that gets used if not overridden by this annotation.
 	 * 		<br>Subclasses can also alter the behavior by overriding these methods.
+	 * 	<li>
+	 * 		The implementation must have one of the following constructors:
+	 * 		<ul>
+	 * 			<li><code><jk>public</jk> T(RestContext)</code>
+	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
+	 * 		</ul>
+	 * 	<li>
+	 * 		Inner classes of the REST resource class are allowed.
 	 * </ul>
 	 *
 	 * <ul class='seealso'>
@@ -202,9 +215,22 @@ public @interface Rest {
 	 * 	<li>
 	 * 		The default resource finder if not specified is {@link ClasspathResourceFinderBasic}.
 	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link ClasspathResourceFinder} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
 	 * 		The {@link RestServlet} class itself implements the {@link ClasspathResourceFinder} interface with the same
 	 * 		functionality as {@link ClasspathResourceFinderBasic} that gets used if not overridden by this annotation.
 	 * 		<br>Subclasses can also alter the behavior by overriding this method.
+	 * 	<li>
+	 * 		The implementation must have one of the following constructors:
+	 * 		<ul>
+	 * 			<li><code><jk>public</jk> T(RestContext)</code>
+	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
+	 * 		</ul>
+	 * 	<li>
+	 * 		Inner classes of the REST resource class are allowed.
 	 * </ul>
 	 *
 	 * <ul class='seealso'>
@@ -405,9 +431,22 @@ public @interface Rest {
 	 * 	<li>
 	 * 		The default info provider if not specified is {@link BasicRestInfoProvider}.
 	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link RestInfoProvider} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
 	 * 		The {@link RestServlet} class itself implements the {@link RestInfoProvider} interface with the same
 	 * 		functionality as {@link BasicRestInfoProvider} that gets used if not overridden by this annotation.
 	 * 		<br>Subclasses can also alter the behavior by overriding these methods.
+	 * 	<li>
+	 * 		The implementation must have one of the following constructors:
+	 * 		<ul>
+	 * 			<li><code><jk>public</jk> T(RestContext)</code>
+	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
+	 * 		</ul>
+	 * 	<li>
+	 * 		Inner classes of the REST resource class are allowed.
 	 * </ul>
 	 *
 	 * <ul class='seealso'>
@@ -423,9 +462,22 @@ public @interface Rest {
 	 * 	<li>
 	 * 		The default call logger if not specified is {@link BasicRestCallLogger}.
 	 * 	<li>
+	 * 		The resource class itself will be used if it implements the {@link RestCallLogger} interface and not
+	 * 		explicitly overridden via this annotation.
+	 * 	<li>
 	 * 		The {@link RestServlet} class itself implements the {@link RestCallLogger} interface with the same
 	 * 		functionality as {@link BasicRestCallLogger} that gets used if not overridden by this annotation.
 	 * 		<br>Subclasses can also alter the behavior by overriding this method.
+	 * 	<li>
+	 * 		The implementation must have one of the following constructors:
+	 * 		<ul>
+	 * 			<li><code><jk>public</jk> T(RestContext)</code>
+	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
+	 * 		</ul>
+	 * 	<li>
+	 * 		Inner classes of the REST resource class are allowed.
 	 * </ul>
 	 *
 	 * <ul class='seealso'>
@@ -574,7 +626,7 @@ public @interface Rest {
 	 * The typical usage is to define a path to a child resource relative to the parent resource.
 	 *
 	 * <h5 class='figure'>Example:</h5>
-	 * <p class='bpcode'>
+	 * <p class='bcode w800'>
 	 * 	<ja>@Rest</ja>(
 	 * 		children={ChildResource.<jk>class</jk>}
 	 * 	)
@@ -612,7 +664,7 @@ public @interface Rest {
 	 * Spring Boot initializer class:
 	 *
 	 * <h5 class='figure'>Example:</h5>
-	 * <p class='bpcode'>
+	 * <p class='bcode'>
 	 * 	<ja>@SpringBootApplication</ja>
 	 * 	<ja>@Controller</ja>
 	 * 	<jk>public class</jk> App {
@@ -647,7 +699,7 @@ public @interface Rest {
 	 * or access through the {@link RestRequest#getPathMatch()} method.
 	 *
 	 * <h5 class='figure'>Example:</h5>
-	 * <p class='bpcode'>
+	 * <p class='bcode'>
 	 * 	<ja>@Rest</ja>(
 	 * 		path=<js>"/myResource/{foo}/{bar}"</js>
 	 * 	)
@@ -790,6 +842,21 @@ public @interface Rest {
 	 * <p>
 	 * The resolver used for resolving child resources.
 	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		Unless overridden, resource resolvers are inherited from ascendant resources.
+	 * 	<li>
+	 * 		The implementation must have one of the following constructors:
+	 * 		<ul>
+	 * 			<li><code><jk>public</jk> T(RestContext)</code>
+	 * 			<li><code><jk>public</jk> T()</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>(RestContext)</code>
+	 * 			<li><code><jk>public static</jk> <jsm>create</jsm>()</code>
+	 * 		</ul>
+	 * 	<li>
+	 * 		Inner classes of the REST resource class are allowed.
+	 * </ul>
+	 *
 	 * <ul class='seealso'>
 	 * 	<li class='jf'>{@link RestContext#REST_resourceResolver}
 	 * </ul>
@@ -1287,6 +1354,8 @@ public @interface Rest {
 	 * <ul class='spaced-list'>
 	 * 	<li>
 	 * 		HTTP request/response bodies are cached in memory for logging purposes.
+	 * 	<li>
+	 * 		HTTP requests/responses are logged to the registered {@link RestCallLogger}.
 	 * </ul>
 	 *
 	 * <p>
@@ -1301,6 +1370,9 @@ public @interface Rest {
 	 * 	<li>
 	 * 		Supports {@doc DefaultRestSvlVariables}
 	 * 		(e.g. <js>"$L{my.localized.variable}"</js>).
+	 * 	<li>
+	 * 		These debug settings can be overridden by the {@link Rest#debugOn()} annotation or at runtime by directly
+	 * 		calling {@link RestRequest#setDebug()}.
 	 * </ul>
 	 *
 	 * <ul class='seealso'>
@@ -1308,4 +1380,121 @@ public @interface Rest {
 	 * </ul>
 	 */
 	String debug() default "";
+
+	/**
+	 * Enable debug mode on specified classes/methods.
+	 *
+	 * <p>
+	 * Enables the following:
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		HTTP request/response bodies are cached in memory for logging purposes on matching classes and methods.
+	 * 	<li>
+	 * 		HTTP requests/responses are logged to the registered {@link RestCallLogger}.
+	 * </ul>
+	 *
+	 * <p>
+	 * Consists of a comma-delimited list of strings of the following forms:
+	 * <ul>
+	 * 	<li><js>"class-identifier"</js> - Enable debug on the specified class.
+	 * 	<li><js>"class-identifier=[true|false|per-request]"</js> - Explicitly enable debug on the specified class.
+	 * 	<li><js>"method-identifier"</js> - Enable debug on the specified class.
+	 * 	<li><js>"method-identifier=[true|false|per-request]"</js> - Explicitly enable debug on the specified class.
+	 * </ul>
+	 *
+	 * <p>
+	 * Class identifiers can be any of the following forms:
+	 * <ul>
+	 * 	<li>Fully qualified:
+	 * 		<ul>
+	 * 			<li><js>"com.foo.MyClass"</js>
+	 * 		</ul>
+	 * 	<li>Fully qualified inner class:
+	 * 		<ul>
+	 * 			<li><js>"com.foo.MyClass$Inner1$Inner2"</js>
+	 * 		</ul>
+	 * 	<li>Simple:
+	 * 		<ul>
+	 * 			<li><js>"MyClass"</js>
+	 * 		</ul>
+	 * 	<li>Simple inner:
+	 * 		<ul>
+	 * 			<li><js>"MyClass$Inner1$Inner2"</js>
+	 * 			<li><js>"Inner1$Inner2"</js>
+	 * 			<li><js>"Inner2"</js>
+	 * 		</ul>
+	 * </ul>
+	 *
+	 * <p>
+	 * Method identifiers can be any of the following forms:
+	 * <ul>
+	 * 	<li>Fully qualified with args:
+	 * 		<ul>
+	 * 			<li><js>"com.foo.MyClass.myMethod(String,int)"</js>
+	 * 			<li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js>
+	 * 			<li><js>"com.foo.MyClass.myMethod()"</js>
+	 * 		</ul>
+	 * 	<li>Fully qualified:
+	 * 		<ul>
+	 * 			<li><js>"com.foo.MyClass.myMethod"</js>
+	 * 		</ul>
+	 * 	<li>Simple with args:
+	 * 		<ul>
+	 * 			<li><js>"MyClass.myMethod(String,int)"</js>
+	 * 			<li><js>"MyClass.myMethod(java.lang.String,int)"</js>
+	 * 			<li><js>"MyClass.myMethod()"</js>
+	 * 		</ul>
+	 * 	<li>Simple:
+	 * 		<ul>
+	 * 			<li><js>"MyClass.myMethod"</js>
+	 * 		</ul>
+	 * 	<li>Simple inner class:
+	 * 		<ul>
+	 * 			<li><js>"MyClass$Inner1$Inner2.myMethod"</js>
+	 * 			<li><js>"Inner1$Inner2.myMethod"</js>
+	 * 			<li><js>"Inner2.myMethod"</js>
+	 * 		</ul>
+	 * </ul>
+	 *
+	 * <h5 class='figure'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Turn on debug per-request on the class and always on the doX() method</jc>.
+	 * 	<ja>@Rest</ja>(
+	 * 		debugOn=<js>"MyResource=per-request,Mysource.doX=true"</js>
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {
+	 *
+	 *		<ja>@RestMethod</ja>
+	 *		<jk>public void</jk> String doX() {
+	 *			...
+	 *		}
+	 * </p>
+	 *
+	 * <p>
+	 * A more-typical scenario is to pull this setting from an external source such as system property or environment
+	 * variable:
+	 *
+	 * <h5 class='figure'>Example:</h5
+	 * <p class='bcode w800'>
+	 * 	<ja>@Rest</ja>(
+	 * 		debugOn=<js>"$E{DEBUG_ON_SETTINGS}"</js>
+	 * 	)
+	 * 	<jk>public class</jk> MyResource {...}
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		Supports {@doc DefaultRestSvlVariables}
+	 * 		(e.g. <js>"$L{my.localized.variable}"</js>).
+	 * 	<li>
+	 * 		These debug settings override the settings define via {@link Rest#debug()} and {@link RestMethod#debug()}.
+	 * 	<li>
+	 * 		These debug settings can be overridden at runtime by directly calling {@link RestRequest#setDebug()}.
+	 * </ul>
+	 *
+	 * <ul class='seealso'>
+	 * 	<li class='jf'>{@link RestContext#REST_debugOn}
+	 * </ul>
+	 */
+	String debugOn() default "";
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestConfigApply.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestConfigApply.java
index 15e72f1..db6ff27 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestConfigApply.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestConfigApply.java
@@ -290,9 +290,11 @@ public class RestConfigApply extends ConfigApply<Rest> {
 		if (! a.maxInput().isEmpty())
 			psb.set(REST_maxInput, string(a.maxInput()));
 
-		if (! a.debug().isEmpty()) {
-			psb.set(REST_debug, a.debug());
-		}
+		if (! a.debug().isEmpty())
+			psb.set(REST_debug, string(a.debug()));
+
+		if (! a.debugOn().isEmpty())
+			psb.set(REST_debugOn, string(a.debugOn()));
 
 		psb.addTo(REST_mimeTypes, strings(a.mimeTypes()));
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
index f34ca28..5f9b0f8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
@@ -174,6 +174,15 @@ public @interface RestMethod {
 	 * 		Request/response messages are automatically logged.
 	 * </ul>
 	 *
+	 * <p>
+	 * Possible values (case insensitive):
+	 * <ul>
+	 * 	<li><js>"true"</js> - Debug is enabled for all requests.
+	 * 	<li><js>"false"</js> - Debug is disabled for all requests.
+	 * 	<li><js>"per-request"</js> - Debug is enabled only for requests that have a <c class='snippet'>X-Debug: true</c> header.
+	 * 	<li><js>""</js> (or anything else) - Debug mode is inherited from class.
+	 * </ul>
+	 *
 	 * <ul class='notes'>
 	 * 	<li>
 	 * 		Supports {@doc DefaultRestSvlVariables}
@@ -181,7 +190,7 @@ public @interface RestMethod {
 	 * </ul>
 	 *
 	 * <ul class='seealso'>
-	 * 	<li class='jf'>{@link RestMethodContext#RESTMETHOD_debug}
+	 * 	<li class='jf'>{@link RestContext#REST_debug}
 	 * </ul>
 	 */
 	String debug() default "";
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java
index d466efc..9e5dad6 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java
@@ -222,16 +222,16 @@ public class RestMethodConfigApply extends ConfigApply<RestMethod> {
 		}
 
 		if (! a.method().isEmpty())
-			psb.set(RESTMETHOD_httpMethod, a.method());
+			psb.set(RESTMETHOD_httpMethod, string(a.method()));
 
 		if (! a.name().isEmpty())
-			psb.set(RESTMETHOD_httpMethod, a.name());
+			psb.set(RESTMETHOD_httpMethod, string(a.name()));
 
 		if (a.priority() != 0)
 			psb.set(RESTMETHOD_priority, a.priority());
 
 		if (! a.debug().isEmpty())
-			psb.set(RESTMETHOD_debug, a.debug());
+			psb.set(RESTMETHOD_debug, string(a.debug()));
 
 		if (! AnnotationUtils.empty(a.logging())) {
 			Logging al = a.logging();