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/07/29 17:49:51 UTC

[juneau] branch master updated: RestContext improvements

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 9ffed80  RestContext improvements
9ffed80 is described below

commit 9ffed80f4d4aaffbb15de155a27c80c4a471f899
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Wed Jul 29 13:49:40 2020 -0400

    RestContext improvements
---
 .../src/main/javadoc/resources/fragments/toc.html  |   2 +-
 .../apache/juneau/rest/mock2/MockRestClient.java   |   7 +-
 .../juneau/rest/BasicRestInfoProviderTest.java     |   4 +-
 .../apache/juneau/rest/testutils/TestUtils.java    |   2 +-
 .../java/org/apache/juneau/rest/BasicRest.java     |  53 +-
 .../apache/juneau/rest/BasicRestCallHandler.java   | 418 --------------
 .../main/java/org/apache/juneau/rest/RestCall.java |  26 +-
 .../org/apache/juneau/rest/RestCallHandler.java    |  11 +-
 .../java/org/apache/juneau/rest/RestContext.java   | 602 +++++++++++++++------
 .../org/apache/juneau/rest/RestContextBuilder.java |  33 +-
 .../org/apache/juneau/rest/RestMethodContext.java  |   4 +-
 .../java/org/apache/juneau/rest/RestRequest.java   |  11 +-
 .../java/org/apache/juneau/rest/RestResponse.java  |  13 +-
 .../java/org/apache/juneau/rest/RestServlet.java   |  54 +-
 .../org/apache/juneau/rest/annotation/Rest.java    |  31 +-
 .../juneau/rest/mock/MockServletRequest.java       |   2 +-
 16 files changed, 506 insertions(+), 767 deletions(-)

diff --git a/juneau-doc/src/main/javadoc/resources/fragments/toc.html b/juneau-doc/src/main/javadoc/resources/fragments/toc.html
index 4280ef9..3fc3365 100644
--- a/juneau-doc/src/main/javadoc/resources/fragments/toc.html
+++ b/juneau-doc/src/main/javadoc/resources/fragments/toc.html
@@ -260,7 +260,7 @@
 		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.Guards'>Guards</a></p>
 		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.RoleGuards'>Role guards</a><span class='update'>8.1.0-new</span></p>
 		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.Converters'>Converters</a></p>
-		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.Messages'>Messages</a></p>
+		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.Messages'>Messages</a><span class='update'><b>8.1.4-updated</b></span></p>
 		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.Encoders'>Encoders</a></p>
 		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.SvlVariables'>SVL Variables</a></p>
 		<li><p><a class='doclink' href='{OVERVIEW_URL}#juneau-rest-server.ConfigurationFiles'>Configuration Files</a></p>
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
index b70358c..634c1ea 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock2/MockRestClient.java
@@ -31,7 +31,6 @@ import org.apache.juneau.*;
 import org.apache.juneau.http.remote.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.*;
-import org.apache.juneau.rest.RestCallHandler;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.client2.*;
 import org.apache.juneau.rest.client2.RestRequest;
@@ -46,7 +45,7 @@ import org.apache.juneau.rest.client2.RestRequest;
  * 	The class itself extends from {@link RestClient} providing it with the rich feature set of that API and combines
  * 	it with the Apache HttpClient {@link HttpClientConnection} interface for processing requests.
  *  The class converts {@link HttpRequest} objects to instances of {@link MockServletRequest} and {@link MockServletResponse} which are passed directly
- *  to the call handler on the resource class {@link RestCallHandler#execute(HttpServletRequest,HttpServletResponse)}.
+ *  to the call handler on the resource class {@link RestContext#execute(HttpServletRequest,HttpServletResponse)}.
  *  In effect, you're fully testing your REST API as if it were running in a live servlet container, yet not
  *  actually having to run in a servlet container.
  *  All aspects of the client and server side code are tested, yet no servlet container is required.  The actual
@@ -752,7 +751,7 @@ public class MockRestClient extends RestClient implements HttpClientConnection {
 	public HttpResponse receiveResponseHeader() throws HttpException, IOException {
 		try {
 			MockServletResponse res = MockServletResponse.create();
-			restBeanCtx.getCallHandler().execute(sreq.get(), res);
+			restBeanCtx.execute(sreq.get(), res);
 
 			// If the status isn't set, something's broken.
 			if (res.getStatus() == 0)
@@ -772,7 +771,7 @@ public class MockRestClient extends RestClient implements HttpClientConnection {
 
 			return response;
 		} catch (Exception e) {
-			throw new HttpException(e.getMessage(), e);
+			throw new HttpException(emptyIfNull(e.getMessage()), e);
 		}
 	}
 
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
index dc18e31..8f441dc 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/BasicRestInfoProviderTest.java
@@ -48,14 +48,14 @@ public class BasicRestInfoProviderTest {
 
 	private Swagger getSwaggerWithFile(Object resource) throws Exception {
 		RestContext rc = RestContext.create(resource).classpathResourceFinder(TestClasspathResourceFinder.class).build();
-		RestRequest req = rc.getCallHandler().createRequest(new RestCall(new MockServletRequest(), null));
+		RestRequest req = rc.createRequest(new RestCall(rc, new MockServletRequest(), null));
 		RestInfoProvider ip = rc.getInfoProvider();
 		return ip.getSwagger(req);
 	}
 
 	private static Swagger getSwagger(Object resource) throws Exception {
 		RestContext rc = RestContext.create(resource).build();
-		RestRequest req = rc.getCallHandler().createRequest(new RestCall(new MockServletRequest(), null));
+		RestRequest req = rc.createRequest(new RestCall(rc, new MockServletRequest(), null));
 		RestInfoProvider ip = rc.getInfoProvider();
 		return ip.getSwagger(req);
 	}
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
index 0821e1f..1c3cd18 100755
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/testutils/TestUtils.java
@@ -27,7 +27,7 @@ public class TestUtils extends org.apache.juneau.testutils.TestUtils {
 	public static Swagger getSwagger(Class<?> c) {
 		try {
 			RestContext rc = RestContext.create(c.newInstance()).build();
-			RestRequest req = rc.getCallHandler().createRequest(new RestCall(new MockServletRequest(), null));
+			RestRequest req = rc.createRequest(new RestCall(rc, new MockServletRequest(), null));
 			RestInfoProvider ip = rc.getInfoProvider();
 			return ip.getSwagger(req);
 		} catch (Exception e) {
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 ff842cf..ea762ce 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
@@ -56,11 +56,10 @@ import org.apache.juneau.http.exception.*;
 		"stats: servlet:/stats"
 	}
 )
-public abstract class BasicRest implements BasicUniversalRest, BasicRestMethods, RestCallHandler, RestInfoProvider, RestCallLogger, RestResourceResolver, ResourceFinder {
+public abstract class BasicRest implements BasicUniversalRest, BasicRestMethods, RestInfoProvider, RestCallLogger, RestResourceResolver, ResourceFinder {
 
 	private Logger logger = Logger.getLogger(getClass().getName());
 	private volatile RestContext context;
-	private RestCallHandler callHandler;
 	private RestInfoProvider infoProvider;
 	private RestCallLogger callLogger;
 	private ResourceFinder resourceFinder;
@@ -280,7 +279,6 @@ public abstract class BasicRest implements BasicUniversalRest, BasicRestMethods,
 	@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 BasicResourceFinder();
@@ -509,55 +507,6 @@ public abstract class BasicRest implements BasicUniversalRest, BasicRestMethods,
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
-	// 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
 	//-----------------------------------------------------------------------------------------------------------------
 
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
deleted file mode 100644
index af776d5..0000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++ /dev/null
@@ -1,418 +0,0 @@
-// ***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
-// * with the License.  You may obtain a copy of the License at                                                              *
-// *                                                                                                                         *
-// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
-// *                                                                                                                         *
-// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
-// * specific language governing permissions and limitations under the License.                                              *
-// ***************************************************************************************************************************
-package org.apache.juneau.rest;
-
-import static javax.servlet.http.HttpServletResponse.*;
-import static org.apache.juneau.internal.IOUtils.*;
-import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.rest.Enablement.*;
-
-import java.io.*;
-import java.lang.reflect.*;
-import java.util.*;
-
-import javax.servlet.*;
-import javax.servlet.http.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.http.exception.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.reflect.*;
-import org.apache.juneau.rest.util.*;
-
-/**
- * Default implementation of {@link RestCallHandler}.
- *
- * <p>
- * Subclasses can override these methods to tailor how HTTP REST calls are handled.
- * <br>Subclasses MUST implement a public constructor that takes in a {@link RestContext} object.
- *
- * <ul class='seealso'>
- * 	<li class='jf'>{@link RestContext#REST_callHandler}
- * </ul>
- */
-public class BasicRestCallHandler implements RestCallHandler {
-
-	private final RestContext context;
-	private final Map<String,RestCallRouter> restCallRouters;
-
-	/**
-	 * Constructor.
-	 *
-	 * @param context The resource context.
-	 */
-	public BasicRestCallHandler(RestContext context) {
-		this.context = context;
-		this.restCallRouters = context.getCallRouters();
-	}
-
-	@Override /* RestCallHandler */
-	public RestCall createCall(HttpServletRequest req, HttpServletResponse res) {
-		return new RestCall(req, res).logger(context.getCallLogger()).loggerConfig(context.getCallLoggerConfig());
-	}
-
-	@Override /* RestCallHandler */
-	public RestRequest createRequest(RestCall call) throws ServletException {
-		return new RestRequest(context, call.getRequest());
-	}
-
-	@Override /* RestCallHandler */
-	public RestResponse createResponse(RestCall call) throws ServletException {
-		return new RestResponse(context, call.getRestRequest(), call.getResponse());
-	}
-
-	/**
-	 * The main service method.
-	 *
-	 * <p>
-	 * Subclasses can optionally override this method if they want to tailor the behavior of requests.
-	 *
-	 * @param r1 The incoming HTTP servlet request object.
-	 * @param r2 The incoming HTTP servlet response object.
-	 * @throws ServletException General servlet exception.
-	 * @throws IOException Thrown by underlying stream.
-	 */
-	@Override /* RestCallHandler */
-	public void execute(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, IOException {
-
-		RestCall call = createCall(r1, r2);
-
-		try {
-			context.checkForInitException();
-
-			// If the resource path contains variables (e.g. @Rest(path="/f/{a}/{b}"), then we want to resolve
-			// those variables and push the servletPath to include the resolved variables.  The new pathInfo will be
-			// the remainder after the new servletPath.
-			// Only do this for the top-level resource because the logic for child resources are processed next.
-			if (context.pathPattern.hasVars() && context.getParentContext() == null) {
-				String sp = call.getServletPath();
-				String pi = call.getPathInfoUndecoded();
-				UrlPathInfo upi2 = new UrlPathInfo(pi == null ? sp : sp + pi);
-				UrlPathPatternMatch uppm = context.pathPattern.match(upi2);
-				if (uppm != null && ! uppm.hasEmptyVars()) {
-					RequestPath.addPathVars(call.getRequest(), uppm.getVars());
-					call.request(
-						new OverrideableHttpServletRequest(call.getRequest())
-							.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
-							.servletPath(uppm.getPrefix())
-					);
-				} else {
-					call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
-					return;
-				}
-			}
-
-			// If this resource has child resources, try to recursively call them.
-			String pi = call.getPathInfoUndecoded();
-			if (context.hasChildResources() && pi != null && ! pi.equals("/")) {
-				for (RestContext rc : context.getChildResources().values()) {
-					UrlPathPattern upp = rc.pathPattern;
-					UrlPathPatternMatch uppm = upp.match(call.getUrlPathInfo());
-					if (uppm != null) {
-						if (! uppm.hasEmptyVars()) {
-							RequestPath.addPathVars(call.getRequest(), uppm.getVars());
-							HttpServletRequest childRequest = new OverrideableHttpServletRequest(call.getRequest())
-								.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
-								.servletPath(call.getServletPath() + uppm.getPrefix());
-							rc.getCallHandler().execute(childRequest, call.getResponse());
-						} else {
-							call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
-						}
-						return;
-					}
-				}
-			}
-
-			if (isDebug(call))
-				call.debug(true);
-
-			context.startCall(call);
-
-			call.restRequest(createRequest(call));
-			call.restResponse(createResponse(call));
-
-			context.setRequest(call.getRestRequest());
-			context.setResponse(call.getRestResponse());
-
-			StaticFile r = null;
-			if (call.getPathInfoUndecoded() != null) {
-				String p = call.getPathInfoUndecoded().substring(1);
-				if (context.isStaticFile(p)) {
-					r = context.getStaticFile(p);
-					if (! r.exists()) {
-						call.output(null);
-						r = null;
-					}
-				} else if (p.equals("favicon.ico")) {
-					call.output(null);
-				}
-			}
-
-			if (r != null) {
-				call.status(SC_OK);
-				call.output(r);
-			} else {
-
-				// If the specified method has been defined in a subclass, invoke it.
-				int rc = 0;
-				String m = call.getMethod();
-
-				if (restCallRouters.containsKey(m))
-					rc = restCallRouters.get(m).invoke(call);
-
-				if ((rc == 0 || rc == 404) && restCallRouters.containsKey("*"))
-					rc = restCallRouters.get("*").invoke(call);
-
-				// Should be 405 if the URL pattern matched but HTTP method did not.
-				if (rc == 0)
-					for (RestCallRouter rcc : restCallRouters.values())
-						if (rcc.matches(call))
-							rc = SC_METHOD_NOT_ALLOWED;
-
-				// Should be 404 if URL pattern didn't match.
-				if (rc == 0)
-					rc = SC_NOT_FOUND;
-
-				// If not invoked above, see if it's an OPTIONs request
-				if (rc != SC_OK)
-					handleNotFound(call.status(rc));
-
-				if (call.getStatus() == 0)
-					call.status(rc);
-			}
-
-			if (call.hasOutput()) {
-				// Now serialize the output if there was any.
-				// Some subclasses may write to the OutputStream or Writer directly.
-				handleResponse(call);
-			}
-
-
-		} catch (Throwable e) {
-			handleError(call, convertThrowable(e));
-		} finally {
-			context.clearState();
-		}
-
-		call.finish();
-		context.finishCall(call);
-	}
-
-	private boolean isDebug(RestCall call) {
-		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;
-		if (e == PER_REQUEST)
-			return "true".equalsIgnoreCase(call.getRequest().getHeader("X-Debug"));
-		return false;
-	}
-
-	/**
-	 * The main method for serializing POJOs passed in through the {@link RestResponse#setOutput(Object)} method or
-	 * returned by the Java method.
-	 *
-	 * <p>
-	 * Subclasses may override this method if they wish to modify the way the output is rendered or support other output
-	 * formats.
-	 *
-	 * <p>
-	 * The default implementation simply iterates through the response handlers on this resource
-	 * looking for the first one whose {@link ResponseHandler#handle(RestRequest,RestResponse)} method returns
-	 * <jk>true</jk>.
-	 *
-	 * @param call The HTTP call.
-	 * @throws IOException Thrown by underlying stream.
-	 * @throws HttpException Non-200 response.
-	 */
-	@Override /* RestCallHandler */
-	public void handleResponse(RestCall call) throws IOException, HttpException, NotImplemented {
-
-		RestRequest req = call.getRestRequest();
-		RestResponse res = call.getRestResponse();
-
-		// Loop until we find the correct handler for the POJO.
-		for (ResponseHandler h : context.getResponseHandlers())
-			if (h.handle(req, res))
-				return;
-
-		Object output = res.getOutput();
-		throw new NotImplemented("No response handlers found to process output of type '"+(output == null ? null : output.getClass().getName())+"'");
-	}
-
-	/**
-	 * Method that can be subclassed to allow uncaught throwables to be treated as other types of throwables.
-	 *
-	 * <p>
-	 * The default implementation looks at the throwable class name to determine whether it can be converted to another type:
-	 *
-	 * <ul>
-	 * 	<li><js>"*AccessDenied*"</js> - Converted to {@link Unauthorized}.
-	 * 	<li><js>"*Empty*"</js>,<js>"*NotFound*"</js> - Converted to {@link NotFound}.
-	 * </ul>
-	 *
-	 * @param t The thrown object.
-	 * @return The converted thrown object.
-	 */
-	@SuppressWarnings("deprecation")
-	@Override
-	public Throwable convertThrowable(Throwable t) {
-
-		ClassInfo ci = ClassInfo.ofc(t);
-		if (ci.is(InvocationTargetException.class)) {
-			t = ((InvocationTargetException)t).getTargetException();
-			ci = ClassInfo.ofc(t);
-		}
-
-		if (ci.is(HttpRuntimeException.class)) {
-			t = ((HttpRuntimeException)t).getInner();
-			ci = ClassInfo.ofc(t);
-		}
-
-		if (ci.isChildOf(RestException.class) || ci.hasAnnotation(Response.class))
-			return t;
-
-		if (t instanceof ParseException || t instanceof InvalidDataConversionException)
-			return new BadRequest(t);
-
-		String n = t.getClass().getName();
-
-		if (n.contains("AccessDenied") || n.contains("Unauthorized"))
-			return new Unauthorized(t);
-
-		if (n.contains("Empty") || n.contains("NotFound"))
-			return new NotFound(t);
-
-		return t;
-	}
-
-	/**
-	 * Handle the case where a matching method was not found.
-	 *
-	 * <p>
-	 * Subclasses can override this method to provide a 2nd-chance for specifying a response.
-	 * The default implementation will simply throw an exception with an appropriate message.
-	 *
-	 * @param call The HTTP call.
-	 */
-	@Override /* RestCallHandler */
-	public void handleNotFound(RestCall call) throws Exception {
-		String pathInfo = call.getPathInfo();
-		String methodUC = call.getMethod();
-		int rc = call.getStatus();
-		String onPath = pathInfo == null ? " on no pathInfo"  : String.format(" on path '%s'", pathInfo);
-		if (rc == SC_NOT_FOUND)
-			throw new NotFound("Method ''{0}'' not found on resource with matching pattern{1}.", methodUC, onPath);
-		else if (rc == SC_PRECONDITION_FAILED)
-			throw new PreconditionFailed("Method ''{0}'' not found on resource{1} with matching matcher.", methodUC, onPath);
-		else if (rc == SC_METHOD_NOT_ALLOWED)
-			throw new MethodNotAllowed("Method ''{0}'' not found on resource{1}.", methodUC, onPath);
-		else
-			throw new ServletException("Invalid method response: " + rc);
-	}
-
-	/**
-	 * Method for handling response errors.
-	 *
-	 * <p>
-	 * Subclasses can override this method to provide their own custom error response handling.
-	 *
-	 * @param call The rest call.
-	 * @param e The exception that occurred.
-	 * @throws IOException Can be thrown if a problem occurred trying to write to the output stream.
-	 */
-	@Override /* RestCallHandler */
-	@SuppressWarnings("deprecation")
-	public synchronized void handleError(RestCall call, Throwable e) throws IOException {
-
-		call.exception(e);
-
-		if (call.isDebug())
-			e.printStackTrace();
-
-		int occurrence = context == null ? 0 : context.getStackTraceOccurrence(e);
-
-		int code = 500;
-
-		ClassInfo ci = ClassInfo.ofc(e);
-		Response r = ci.getLastAnnotation(Response.class);
-		if (r != null)
-			if (r.code().length > 0)
-				code = r.code()[0];
-
-		RestException e2 = (e instanceof RestException ? (RestException)e : new RestException(e, code)).setOccurrence(occurrence);
-
-		HttpServletRequest req = call.getRequest();
-		HttpServletResponse res = call.getResponse();
-
-		Throwable t = null;
-		if (e instanceof HttpRuntimeException)
-			t = ((HttpRuntimeException)e).getInner();
-		if (t == null)
-			t = e2.getRootCause();
-		if (t != null) {
-			res.setHeader("Exception-Name", stripInvalidHttpHeaderChars(t.getClass().getName()));
-			res.setHeader("Exception-Message", stripInvalidHttpHeaderChars(t.getMessage()));
-		}
-
-		try {
-			res.setContentType("text/plain");
-			res.setHeader("Content-Encoding", "identity");
-			res.setStatus(e2.getStatus());
-
-			PrintWriter w = null;
-			try {
-				w = res.getWriter();
-			} catch (IllegalStateException x) {
-				w = new PrintWriter(new OutputStreamWriter(res.getOutputStream(), UTF8));
-			}
-
-			try (PrintWriter w2 = w) {
-				String httpMessage = RestUtils.getHttpResponseText(e2.getStatus());
-				if (httpMessage != null)
-					w2.append("HTTP ").append(String.valueOf(e2.getStatus())).append(": ").append(httpMessage).append("\n\n");
-				if (context != null && context.isRenderResponseStackTraces())
-					e.printStackTrace(w2);
-				else
-					w2.append(e2.getFullStackMessage(true));
-			}
-
-		} catch (Exception e1) {
-			req.setAttribute("Exception", e1);
-		}
-	}
-
-	/**
-	 * Returns the session objects for the specified request.
-	 *
-	 * <p>
-	 * The default implementation simply returns a single map containing <c>{'req':req}</c>.
-	 *
-	 * @param req The REST request.
-	 * @return The session objects for that request.
-	 */
-	@Override /* RestCallHandler */
-	public Map<String,Object> getSessionObjects(RestRequest req, RestResponse res) {
-		Map<String,Object> m = new HashMap<>();
-		m.put("req", req);
-		m.put("res", res);
-		return m;
-	}
-}
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 3aed6db..4d2dbac 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
@@ -30,6 +30,7 @@ public class RestCall {
 	private HttpServletResponse res;
 	private RestRequest rreq;
 	private RestResponse rres;
+	private RestContext context;
 	private RestMethodContext rmethod;
 	private UrlPathInfo urlPathInfo;
 	private String pathInfoUndecoded;
@@ -40,11 +41,12 @@ public class RestCall {
 	/**
 	 * Constructor.
 	 *
+	 * @param context The REST context object.
 	 * @param req The incoming HTTP servlet request object.
 	 * @param res The incoming HTTP servlet response object.
 	 */
-	public RestCall(HttpServletRequest req, HttpServletResponse res) {
-		request(req).response(res);
+	public RestCall(RestContext context, HttpServletRequest req, HttpServletResponse res) {
+		context(context).request(req).response(res);
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
@@ -76,6 +78,17 @@ public class RestCall {
 	}
 
 	/**
+	 * Overrides the context object on this call.
+	 *
+	 * @param context The context that's creating this call.
+	 * @return This object (for method chaining).
+	 */
+	public RestCall context(RestContext context) {
+		this.context = context;
+		return this;
+	}
+
+	/**
 	 * Sets the method context on this call.
 	 *
 	 * Used for logging statistics on the method.
@@ -377,4 +390,13 @@ public class RestCall {
 			return rreq.isDebug();
 		return false;
 	}
+
+	/**
+	 * Returns the context that created this call.
+	 *
+	 * @return The context that created this call.
+	 */
+	public RestContext getContext() {
+		return context;
+	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
index 9d24fe7..026f059 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCallHandler.java
@@ -22,18 +22,15 @@ import javax.servlet.http.*;
 /**
  * Class that handles the basic lifecycle of an HTTP REST call.
  *
- * <ul class='seealso'>
- * 	<li class='jf'>{@link RestContext#REST_callHandler}
- * </ul>
+ * <div class='warn'>
+ * 	<b>Deprecated</b> - Use {@link RestContext#REST_context} and override methods.
+ * </div>
  */
+@Deprecated
 public interface RestCallHandler {
 
 	/**
 	 * Represents no RestCallHandler.
-	 *
-	 * <p>
-	 * Used on annotation to indicate that the value should be inherited from the parent class, and
-	 * ultimately {@link BasicRestCallHandler} if not specified at any level.
 	 */
 	public interface Null extends RestCallHandler {}
 
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 7677d47..a376186 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
@@ -23,6 +23,7 @@ import static org.apache.juneau.rest.HttpRuntimeException.*;
 import static org.apache.juneau.BasicIllegalArgumentException.*;
 
 import java.io.*;
+import java.lang.reflect.*;
 import java.lang.reflect.Method;
 import java.nio.charset.*;
 import java.time.*;
@@ -397,106 +398,11 @@ public class RestContext extends BeanContext {
 	/**
 	 * Configuration property:  REST call handler.
 	 *
-	 * <h5 class='section'>Property:</h5>
-	 * <ul class='spaced-list'>
-	 * 	<li><b>ID:</b>  {@link org.apache.juneau.rest.RestContext#REST_callHandler REST_callHandler}
-	 * 	<li><b>Name:</b>  <js>"RestContext.callHandler.o"</js>
-	 * 	<li><b>Data type:</b>
-	 * 		<ul>
-	 * 			<li>{@link org.apache.juneau.rest.RestCallHandler}
-	 * 			<li><c>Class&lt;{@link org.apache.juneau.rest.RestCallHandler}&gt;</c>
-	 * 		</ul>
-	 * 	<li><b>Default:</b>  {@link org.apache.juneau.rest.BasicRestCallHandler}
-	 * 	<li><b>Session property:</b>  <jk>false</jk>
-	 * 	<li><b>Annotations:</b>
-	 * 		<ul>
-	 * 			<li class='ja'>{@link org.apache.juneau.rest.annotation.Rest#callHandler()}
-	 * 		</ul>
-	 * 	<li><b>Methods:</b>
-	 * 		<ul>
-	 * 			<li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#callHandler(Class)}
-	 * 			<li class='jm'>{@link org.apache.juneau.rest.RestContextBuilder#callHandler(RestCallHandler)}
-	 * 		</ul>
-	 * </ul>
-	 *
-	 * <h5 class='section'>Description:</h5>
-	 * <p>
-	 * This class handles the basic lifecycle of an HTTP REST call.
-	 * <br>Subclasses can be used to customize how these HTTP calls are handled.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Our customized call handler.</jc>
-	 * 	<jk>public class</jk> MyRestCallHandler <jk>extends</jk> BasicRestCallHandler {
-	 *
-	 * 		<jc>// Must provide this constructor!</jc>
-	 * 		<jk>public</jk> MyRestCallHandler(RestContext context) {
-	 * 			<jk>super</jk>(context);
-	 * 		}
-	 *
-	 * 		<ja>@Override</ja>
-	 * 		<jk>public</jk> RestRequest createRequest(HttpServletRequest req) <jk>throws</jk> ServletException {
-	 * 			<jc>// Low-level handling of requests.</jc>
-	 * 			...
-	 * 		}
-	 *
-	 * 		<ja>@Override</ja>
-	 * 		<jk>public void</jk> handleResponse(RestRequest req, RestResponse res, Object output) <jk>throws</jk> IOException, RestException {
-	 * 			<jc>// Low-level handling of responses.</jc>
-	 * 			...
-	 * 		}
-	 *
-	 * 		<ja>@Override</ja>
-	 * 		<jk>public void</jk> handleNotFound(int rc, RestRequest req, RestResponse res) <jk>throws</jk> Exception {
-	 * 			<jc>// Low-level handling of various error conditions.</jc>
-	 * 			...
-	 * 		}
-	 * 	}
-	 *
-	 * 	<jc>// Option #1 - Registered via annotation resolving to a config file setting with default value.</jc>
-	 * 	<ja>@Rest</ja>(callHandler=MyRestCallHandler.<jk>class</jk>)
-	 * 	<jk>public class</jk> MyResource {
-	 *
-	 * 		<jc>// Option #2 - Registered via builder passed in through resource constructor.</jc>
-	 * 		<jk>public</jk> MyResource(RestContextBuilder builder) <jk>throws</jk> Exception {
-	 *
-	 * 			<jc>// Using method on builder.</jc>
-	 * 			builder.callHandler(MyRestCallHandler.<jk>class</jk>);
-	 *
-	 * 			<jc>// Same, but using property.</jc>
-	 * 			builder.set(<jsf>REST_callHandler</jsf>, MyRestCallHandler.<jk>class</jk>);
-	 * 		}
-	 *
-	 * 		<jc>// Option #3 - Registered via builder passed in through init method.</jc>
-	 * 		<ja>@RestHook</ja>(<jsf>INIT</jsf>)
-	 * 		<jk>public void</jk> init(RestContextBuilder builder) <jk>throws</jk> Exception {
-	 * 			builder.callHandler(MyRestCallHandler.<jk>class</jk>);
-	 * 		}
-	 * 	}
-	 * </p>
-	 *
-	 * <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} and {@link BasicRest} classes implement 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> T <jsm>create</jsm>(RestContext)</code>
-	 * 			<li><code><jk>public static</jk> T <jsm>create</jsm>()</code>
-	 * 		</ul>
-	 * 	<li>
-	 * 		Inner classes of the REST resource class are allowed.
-	 * </ul>
+	 * <div class='warn'>
+	 * 	<b>Deprecated</b> - Use {@link RestContext#REST_context} and override methods.
+	 * </div>
 	 */
+	@Deprecated
 	public static final String REST_callHandler = PREFIX + ".callHandler.o";
 
 	/**
@@ -569,7 +475,7 @@ public class RestContext extends BeanContext {
 	 * 		explicitly overridden via this annotation.
 	 * 	<li>
 	 * 		The {@link RestServlet} and {@link BasicRest} classes implement the {@link RestCallLogger} interface with the same
-	 * 		functionality as {@link BasicRestCallHandler} that gets used if not overridden by this annotation.
+	 * 		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:
@@ -2190,7 +2096,7 @@ public class RestContext extends BeanContext {
 	 * 		<ul>
 	 * 			<li class='jm'>{@link RestContext#isRenderResponseStackTraces() RestContext.isRenderResponseStackTraces()}
 	 * 		</ul>
-	 * 		That method is used by {@link BasicRestCallHandler#handleError(RestCall, Throwable)}.
+	 * 		That method is used by {@link #handleError(RestCall, Throwable)}.
 	 * </ul>
 	 */
 	public static final String REST_renderResponseStackTraces = PREFIX + ".renderResponseStackTraces.b";
@@ -3699,7 +3605,6 @@ public class RestContext extends BeanContext {
 	private final RestCallLogger callLogger;
 	private final RestCallLoggerConfig callLoggerConfig;
 	private final StackTraceDatabase stackTraceDb;
-	private final RestCallHandler callHandler;
 	private final RestInfoProvider infoProvider;
 	private final HttpException initException;
 	private final RestContext parentContext;
@@ -3735,8 +3640,7 @@ public class RestContext extends BeanContext {
 	private final ResourceManager staticResourceManager;
 	@Deprecated private final ConcurrentHashMap<Integer,AtomicInteger> stackTraceHashes = new ConcurrentHashMap<>();
 
-	private final ThreadLocal<RestRequest> req = new ThreadLocal<>();
-	private final ThreadLocal<RestResponse> res = new ThreadLocal<>();
+	private final ThreadLocal<RestCall> call = new ThreadLocal<>();
 
 	private final ReflectionMap<Enablement> debugEnablement;
 
@@ -4198,9 +4102,6 @@ public class RestContext extends BeanContext {
 				childResources.put(path, rc2);
 			}
 
-			Object defaultRestCallHandler = resource instanceof RestCallHandler ? resource : BasicRestCallHandler.class;
-			callHandler = getInstanceProperty(REST_callHandler, resource, RestCallHandler.class, defaultRestCallHandler, resourceResolver, this);
-
 			Object defaultRestInfoProvider = resource instanceof RestInfoProvider ? resource : BasicRestInfoProvider.class;
 			infoProvider = getInstanceProperty(REST_infoProvider, resource, RestInfoProvider.class, defaultRestInfoProvider, resourceResolver, this);
 
@@ -4602,21 +4503,6 @@ public class RestContext extends BeanContext {
 	}
 
 	/**
-	 * Returns the REST call handler used by this resource.
-	 *
-	 * <ul class='seealso'>
-	 * 	<li class='jf'>{@link RestContext#REST_callHandler}
-	 * </ul>
-	 *
-	 * @return
-	 * 	The call handler for this resource.
-	 * 	<br>Never <jk>null</jk>.
-	 */
-	public RestCallHandler getCallHandler() {
-		return callHandler;
-	}
-
-	/**
 	 * Returns a map of HTTP method names to call routers.
 	 *
 	 * @return A map with HTTP method names upper-cased as the keys, and call routers as the values.
@@ -5217,28 +5103,431 @@ public class RestContext extends BeanContext {
 		return rp;
 	}
 
-	/*
-	 * Calls all @RestHook(PRE) methods.
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Call handling
+	//------------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Wraps an incoming servlet request/response pair into a single {@link RestCall} object.
+	 *
+	 * <p>
+	 * This is the first method called by {@link #execute(HttpServletRequest, HttpServletResponse)}.
+	 *
+	 * @param req The rest request.
+	 * @param res The rest response.
+	 * @return The wrapped request/response pair.
 	 */
-	void preCall(RestRequest req, RestResponse res) throws HttpException {
+	protected RestCall createCall(HttpServletRequest req, HttpServletResponse res) {
+		return new RestCall(this, req, res).logger(getCallLogger()).loggerConfig(getCallLoggerConfig());
+	}
+
+	/**
+	 * Creates a {@link RestRequest} object based on the specified incoming {@link HttpServletRequest} object.
+	 *
+	 * <p>
+	 * This method is called immediately after {@link #startCall(RestCall)} has been called.
+	 *
+	 * @param call The current REST call.
+	 * @return The wrapped request object.
+	 * @throws ServletException If any errors occur trying to interpret the request.
+	 */
+	public RestRequest createRequest(RestCall call) throws ServletException {
+		return new RestRequest(call);
+	}
+
+	/**
+	 * Creates a {@link RestResponse} object based on the specified incoming {@link HttpServletResponse} object
+	 * and the request returned by {@link #createRequest(RestCall)}.
+	 *
+	 * @param call The current REST call.
+	 * @return The wrapped response object.
+	 * @throws ServletException If any errors occur trying to interpret the request or response.
+	 */
+	public RestResponse createResponse(RestCall call) throws ServletException {
+		return new RestResponse(call);
+	}
+
+	/**
+	 * The main service method.
+	 *
+	 * <p>
+	 * Subclasses can optionally override this method if they want to tailor the behavior of requests.
+	 *
+	 * @param r1 The incoming HTTP servlet request object.
+	 * @param r2 The incoming HTTP servlet response object.
+	 * @throws ServletException General servlet exception.
+	 * @throws IOException Thrown by underlying stream.
+	 */
+	public void execute(HttpServletRequest r1, HttpServletResponse r2) throws ServletException, IOException {
+
+		RestCall call = createCall(r1, r2);
+
+		// Must be careful not to bleed thread-locals.
+		if (this.call.get() != null)
+			System.err.println("WARNING:  Thread-local call object was not cleaned up from previous request.  " + this + ", thread=["+Thread.currentThread().getId()+"]");
+		this.call.set(call);
+
+		try {
+			checkForInitException();
+
+			// If the resource path contains variables (e.g. @Rest(path="/f/{a}/{b}"), then we want to resolve
+			// those variables and push the servletPath to include the resolved variables.  The new pathInfo will be
+			// the remainder after the new servletPath.
+			// Only do this for the top-level resource because the logic for child resources are processed next.
+			if (pathPattern.hasVars() && getParentContext() == null) {
+				String sp = call.getServletPath();
+				String pi = call.getPathInfoUndecoded();
+				UrlPathInfo upi2 = new UrlPathInfo(pi == null ? sp : sp + pi);
+				UrlPathPatternMatch uppm = pathPattern.match(upi2);
+				if (uppm != null && ! uppm.hasEmptyVars()) {
+					RequestPath.addPathVars(call.getRequest(), uppm.getVars());
+					call.request(
+						new OverrideableHttpServletRequest(call.getRequest())
+							.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
+							.servletPath(uppm.getPrefix())
+					);
+				} else {
+					call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
+					return;
+				}
+			}
+
+			// If this resource has child resources, try to recursively call them.
+			String pi = call.getPathInfoUndecoded();
+			if (hasChildResources() && pi != null && ! pi.equals("/")) {
+				for (RestContext rc : getChildResources().values()) {
+					UrlPathPattern upp = rc.pathPattern;
+					UrlPathPatternMatch uppm = upp.match(call.getUrlPathInfo());
+					if (uppm != null) {
+						if (! uppm.hasEmptyVars()) {
+							RequestPath.addPathVars(call.getRequest(), uppm.getVars());
+							HttpServletRequest childRequest = new OverrideableHttpServletRequest(call.getRequest())
+								.pathInfo(nullIfEmpty(urlDecode(uppm.getSuffix())))
+								.servletPath(call.getServletPath() + uppm.getPrefix());
+							rc.execute(childRequest, call.getResponse());
+						} else {
+							call.debug(isDebug(call)).status(SC_NOT_FOUND).finish();
+						}
+						return;
+					}
+				}
+			}
+
+			if (isDebug(call))
+				call.debug(true);
+
+			startCall(call);
+
+			call.restRequest(createRequest(call));
+			call.restResponse(createResponse(call));
+
+			StaticFile r = null;
+			if (call.getPathInfoUndecoded() != null) {
+				String p = call.getPathInfoUndecoded().substring(1);
+				if (isStaticFile(p)) {
+					r = getStaticFile(p);
+					if (! r.exists()) {
+						call.output(null);
+						r = null;
+					}
+				} else if (p.equals("favicon.ico")) {
+					call.output(null);
+				}
+			}
+
+			if (r != null) {
+				call.status(SC_OK);
+				call.output(r);
+			} else {
+
+				// If the specified method has been defined in a subclass, invoke it.
+				int rc = 0;
+				String m = call.getMethod();
+
+				if (callRouters.containsKey(m))
+					rc = callRouters.get(m).invoke(call);
+
+				if ((rc == 0 || rc == 404) && callRouters.containsKey("*"))
+					rc = callRouters.get("*").invoke(call);
+
+				// Should be 405 if the URL pattern matched but HTTP method did not.
+				if (rc == 0)
+					for (RestCallRouter rcc : callRouters.values())
+						if (rcc.matches(call))
+							rc = SC_METHOD_NOT_ALLOWED;
+
+				// Should be 404 if URL pattern didn't match.
+				if (rc == 0)
+					rc = SC_NOT_FOUND;
+
+				// If not invoked above, see if it's an OPTIONs request
+				if (rc != SC_OK)
+					handleNotFound(call.status(rc));
+
+				if (call.getStatus() == 0)
+					call.status(rc);
+			}
+
+			if (call.hasOutput()) {
+				// Now serialize the output if there was any.
+				// Some subclasses may write to the OutputStream or Writer directly.
+				handleResponse(call);
+			}
+
+
+		} catch (Throwable e) {
+			handleError(call, convertThrowable(e));
+		} finally {
+			clearState();
+		}
+
+		call.finish();
+		finishCall(call);
+	}
+
+	private boolean isDebug(RestCall call) {
+		Enablement e = null;
+		RestMethodContext mc = call.getRestMethodContext();
+		if (mc != null)
+			e = mc.getDebug();
+		if (e == null)
+			e = getDebug();
+		if (e == TRUE)
+			return true;
+		if (e == FALSE)
+			return false;
+		if (e == PER_REQUEST)
+			return "true".equalsIgnoreCase(call.getRequest().getHeader("X-Debug"));
+		return false;
+	}
+
+	/**
+	 * The main method for serializing POJOs passed in through the {@link RestResponse#setOutput(Object)} method or
+	 * returned by the Java method.
+	 *
+	 * <p>
+	 * Subclasses may override this method if they wish to modify the way the output is rendered or support other output
+	 * formats.
+	 *
+	 * <p>
+	 * The default implementation simply iterates through the response handlers on this resource
+	 * looking for the first one whose {@link ResponseHandler#handle(RestRequest,RestResponse)} method returns
+	 * <jk>true</jk>.
+	 *
+	 * @param call The HTTP call.
+	 * @throws IOException Thrown by underlying stream.
+	 * @throws HttpException Non-200 response.
+	 * @throws NotImplemented No registered response handlers could handle the call.
+	 */
+	public void handleResponse(RestCall call) throws IOException, HttpException, NotImplemented {
+
+		RestRequest req = call.getRestRequest();
+		RestResponse res = call.getRestResponse();
+
+		// Loop until we find the correct handler for the POJO.
+		for (ResponseHandler h : getResponseHandlers())
+			if (h.handle(req, res))
+				return;
+
+		Object output = res.getOutput();
+		throw new NotImplemented("No response handlers found to process output of type '"+(output == null ? null : output.getClass().getName())+"'");
+	}
+
+	/**
+	 * Method that can be subclassed to allow uncaught throwables to be treated as other types of throwables.
+	 *
+	 * <p>
+	 * The default implementation looks at the throwable class name to determine whether it can be converted to another type:
+	 *
+	 * <ul>
+	 * 	<li><js>"*AccessDenied*"</js> - Converted to {@link Unauthorized}.
+	 * 	<li><js>"*Empty*"</js>,<js>"*NotFound*"</js> - Converted to {@link NotFound}.
+	 * </ul>
+	 *
+	 * @param t The thrown object.
+	 * @return The converted thrown object.
+	 */
+	@SuppressWarnings("deprecation")
+	public Throwable convertThrowable(Throwable t) {
+
+		ClassInfo ci = ClassInfo.ofc(t);
+		if (ci.is(InvocationTargetException.class)) {
+			t = ((InvocationTargetException)t).getTargetException();
+			ci = ClassInfo.ofc(t);
+		}
+
+		if (ci.is(HttpRuntimeException.class)) {
+			t = ((HttpRuntimeException)t).getInner();
+			ci = ClassInfo.ofc(t);
+		}
+
+		if (ci.isChildOf(RestException.class) || ci.hasAnnotation(Response.class))
+			return t;
+
+		if (t instanceof ParseException || t instanceof InvalidDataConversionException)
+			return new BadRequest(t);
+
+		String n = t.getClass().getName();
+
+		if (n.contains("AccessDenied") || n.contains("Unauthorized"))
+			return new Unauthorized(t);
+
+		if (n.contains("Empty") || n.contains("NotFound"))
+			return new NotFound(t);
+
+		return t;
+	}
+
+	/**
+	 * Handle the case where a matching method was not found.
+	 *
+	 * <p>
+	 * Subclasses can override this method to provide a 2nd-chance for specifying a response.
+	 * The default implementation will simply throw an exception with an appropriate message.
+	 *
+	 * @param call The HTTP call.
+	 * @throws Exception Any exception can be thrown.
+	 */
+	public void handleNotFound(RestCall call) throws Exception {
+		String pathInfo = call.getPathInfo();
+		String methodUC = call.getMethod();
+		int rc = call.getStatus();
+		String onPath = pathInfo == null ? " on no pathInfo"  : String.format(" on path '%s'", pathInfo);
+		if (rc == SC_NOT_FOUND)
+			throw new NotFound("Method ''{0}'' not found on resource with matching pattern{1}.", methodUC, onPath);
+		else if (rc == SC_PRECONDITION_FAILED)
+			throw new PreconditionFailed("Method ''{0}'' not found on resource{1} with matching matcher.", methodUC, onPath);
+		else if (rc == SC_METHOD_NOT_ALLOWED)
+			throw new MethodNotAllowed("Method ''{0}'' not found on resource{1}.", methodUC, onPath);
+		else
+			throw new ServletException("Invalid method response: " + rc);
+	}
+
+	/**
+	 * Method for handling response errors.
+	 *
+	 * <p>
+	 * Subclasses can override this method to provide their own custom error response handling.
+	 *
+	 * @param call The rest call.
+	 * @param e The exception that occurred.
+	 * @throws IOException Can be thrown if a problem occurred trying to write to the output stream.
+	 */
+	@SuppressWarnings("deprecation")
+	public synchronized void handleError(RestCall call, Throwable e) throws IOException {
+
+		call.exception(e);
+
+		if (call.isDebug())
+			e.printStackTrace();
+
+		int occurrence = getStackTraceOccurrence(e);
+
+		int code = 500;
+
+		ClassInfo ci = ClassInfo.ofc(e);
+		Response r = ci.getLastAnnotation(Response.class);
+		if (r != null)
+			if (r.code().length > 0)
+				code = r.code()[0];
+
+		RestException e2 = (e instanceof RestException ? (RestException)e : new RestException(e, code)).setOccurrence(occurrence);
+
+		HttpServletRequest req = call.getRequest();
+		HttpServletResponse res = call.getResponse();
+
+		Throwable t = null;
+		if (e instanceof HttpRuntimeException)
+			t = ((HttpRuntimeException)e).getInner();
+		if (t == null)
+			t = e2.getRootCause();
+		if (t != null) {
+			res.setHeader("Exception-Name", stripInvalidHttpHeaderChars(t.getClass().getName()));
+			res.setHeader("Exception-Message", stripInvalidHttpHeaderChars(t.getMessage()));
+		}
+
+		try {
+			res.setContentType("text/plain");
+			res.setHeader("Content-Encoding", "identity");
+			res.setStatus(e2.getStatus());
+
+			PrintWriter w = null;
+			try {
+				w = res.getWriter();
+			} catch (IllegalStateException x) {
+				w = new PrintWriter(new OutputStreamWriter(res.getOutputStream(), UTF8));
+			}
+
+			try (PrintWriter w2 = w) {
+				String httpMessage = RestUtils.getHttpResponseText(e2.getStatus());
+				if (httpMessage != null)
+					w2.append("HTTP ").append(String.valueOf(e2.getStatus())).append(": ").append(httpMessage).append("\n\n");
+				if (isRenderResponseStackTraces())
+					e.printStackTrace(w2);
+				else
+					w2.append(e2.getFullStackMessage(true));
+			}
+
+		} catch (Exception e1) {
+			req.setAttribute("Exception", e1);
+		}
+	}
+
+	/**
+	 * Returns the session objects for the specified request.
+	 *
+	 * <p>
+	 * The default implementation simply returns a single map containing <c>{'req':req,'res',res}</c>.
+	 *
+	 * @param call The current REST call.
+	 * @return The session objects for that request.
+	 */
+	public Map<String,Object> getSessionObjects(RestCall call) {
+		Map<String,Object> m = new HashMap<>();
+		m.put("req", call.getRequest());
+		m.put("res", call.getResponse());
+		return m;
+	}
+
+	/**
+	 * Called at the start of a request to invoke all {@link HookEvent#START_CALL} methods.
+	 *
+	 * @param call The current request.
+	 */
+	protected void startCall(RestCall call) {
+		for (int i = 0; i < startCallMethods.length; i++)
+			startOrFinish(resource, startCallMethods[i], startCallMethodParams[i], call.getRequest(), call.getResponse());
+	}
+
+	/**
+	 * Called during a request to invoke all {@link HookEvent#PRE_CALL} methods.
+	 *
+	 * @param call The current request.
+	 * @throws HttpException If thrown from call methods.
+	 */
+	protected void preCall(RestCall call) throws HttpException {
 		for (int i = 0; i < preCallMethods.length; i++)
-			preOrPost(resource, preCallMethods[i], preCallMethodParams[i], req, res);
+			preOrPost(resource, preCallMethods[i], preCallMethodParams[i], call);
 	}
 
-	/*
-	 * Calls all @RestHook(POST) methods.
+	/**
+	 * Called during a request to invoke all {@link HookEvent#POST_CALL} methods.
+	 *
+	 * @param call The current request.
+	 * @throws HttpException If thrown from call methods.
 	 */
-	void postCall(RestRequest req, RestResponse res) throws HttpException {
+	protected void postCall(RestCall call) throws HttpException {
 		for (int i = 0; i < postCallMethods.length; i++)
-			preOrPost(resource, postCallMethods[i], postCallMethodParams[i], req, res);
+			preOrPost(resource, postCallMethods[i], postCallMethodParams[i], call);
 	}
 
-	private static void preOrPost(Object resource, MethodInvoker m, RestMethodParam[] mp, RestRequest req, RestResponse res) throws HttpException {
+	private static void preOrPost(Object resource, MethodInvoker m, RestMethodParam[] mp, RestCall call) throws HttpException {
 		if (m != null) {
 			Object[] args = new Object[mp.length];
 			for (int i = 0; i < mp.length; i++) {
 				try {
-					args[i] = mp[i].resolve(req, res);
+					args[i] = mp[i].resolve(call.getRestRequest(), call.getRestResponse());
 				} catch (Exception e) {
 					throw toHttpException(e, BadRequest.class, "Invalid data conversion.  Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.", mp[i].getParamType().name(), mp[i].getName(), mp[i].getType(), m.getDeclaringClass().getName(), m.getName());
 				}
@@ -5251,18 +5540,15 @@ public class RestContext extends BeanContext {
 		}
 	}
 
-	/*
-	 * Calls all @RestHook(START) methods.
-	 */
-	void startCall(RestCall call) {
-		for (int i = 0; i < startCallMethods.length; i++)
-			startOrFinish(resource, startCallMethods[i], startCallMethodParams[i], call.getRequest(), call.getResponse());
-	}
-
-	/*
-	 * Calls all @RestHook(FINISH) methods.
+	/**
+	 * Called at the end of a request to invoke all {@link HookEvent#END_CALL} methods.
+	 *
+	 * <p>
+	 * This is the very last method called in {@link #execute(HttpServletRequest, HttpServletResponse)}.
+	 *
+	 * @param call The current request.
 	 */
-	void finishCall(RestCall call) {
+	protected void finishCall(RestCall call) {
 		for (int i = 0; i < endCallMethods.length; i++)
 			startOrFinish(resource, endCallMethods[i], endCallMethodParams[i], call.getRequest(), call.getResponse());
 	}
@@ -5285,7 +5571,7 @@ public class RestContext extends BeanContext {
 	}
 
 	/**
-	 * Calls all @RestHook(POST_INIT) methods in parent-to-child order.
+	 * Called during servlet initialization to invoke all {@link HookEvent#POST_INIT} methods.
 	 *
 	 * @return This object (for method chaining).
 	 * @throws ServletException Error occurred.
@@ -5299,7 +5585,7 @@ public class RestContext extends BeanContext {
 	}
 
 	/**
-	 * Calls all @RestHook(POST_INIT_CHILD_FIRST) methods in child-to-parent order.
+	 * Called during servlet initialization to invoke all {@link HookEvent#POST_INIT_CHILD_FIRST} methods.
 	 *
 	 * @return This object (for method chaining).
 	 * @throws ServletException Error occurred.
@@ -5334,7 +5620,7 @@ public class RestContext extends BeanContext {
 	}
 
 	/**
-	 * Calls {@link Servlet#destroy()} on any child resources defined on this resource.
+	 * Called during servlet initialization to invoke all {@link HookEvent#DESTROY} methods.
 	 */
 	protected void destroy() {
 		for (int i = 0; i < destroyMethods.length; i++) {
@@ -5358,14 +5644,8 @@ public class RestContext extends BeanContext {
 	 * @return The HTTP request object, or <jk>null</jk> if it hasn't been created.
 	 */
 	public RestRequest getRequest() {
-		return req.get();
-	}
-
-	void setRequest(RestRequest req) {
-		// Must be careful not to bleed thread-locals.
-		if (this.req.get() != null)
-			System.err.println("WARNING:  Thread-local request object was not cleaned up from previous request.  " + this + ", thread=["+Thread.currentThread().getId()+"]");
-		this.req.set(req);
+		RestCall rc = call.get();
+		return rc == null ? null : rc.getRestRequest();
 	}
 
 	/**
@@ -5374,14 +5654,8 @@ public class RestContext extends BeanContext {
 	 * @return The HTTP response object, or <jk>null</jk> if it hasn't been created.
 	 */
 	public RestResponse getResponse() {
-		return res.get();
-	}
-
-	void setResponse(RestResponse res) {
-		// Must be careful not to bleed thread-locals.
-		if (this.res.get() != null)
-			System.err.println("WARNING:  Thread-local response object was not cleaned up from previous request.  " + this + ", thread=["+Thread.currentThread().getId()+"]");
-		this.res.set(res);
+		RestCall rc = call.get();
+		return rc == null ? null : rc.getRestResponse();
 	}
 
 	/**
@@ -5415,8 +5689,7 @@ public class RestContext extends BeanContext {
 	 * This should always be called in a finally block in the RestServlet.
 	 */
 	void clearState() {
-		req.remove();
-		res.remove();
+		call.remove();
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
@@ -5431,7 +5704,6 @@ public class RestContext extends BeanContext {
 				.a("allowedMethodHeader", allowedMethodHeaders)
 				.a("allowedMethodParams", allowedMethodParams)
 				.a("allowedHeaderParams", allowedHeaderParams)
-				.a("callHandler", callHandler)
 				.a("clientVersionHeader", clientVersionHeader)
 				.a("consumes", consumes)
 				.a("infoProvider", infoProvider)
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 d9a0d14..674f631 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
@@ -576,20 +576,13 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	/**
 	 * <i><l>RestContext</l> configuration property:&emsp;</i>  REST call handler.
 	 *
-	 * <p>
-	 * This class handles the basic lifecycle of an HTTP REST call.
-	 * <br>Subclasses can be used to customize how these HTTP calls are handled.
-	 *
-	 * <ul class='seealso'>
-	 * 	<li class='jf'>{@link RestContext#REST_callHandler}
-	 * </ul>
-	 *
-	 * @param value
-	 * 	The new value for this setting.
-	 * 	<br>The default is {@link BasicRestCallHandler}.
-	 * @return This object (for method chaining).
+	 * <div class='warn'>
+	 * 	<b>Deprecated</b> - Use {@link RestContext#REST_context} and override methods.
+	 * </div>
 	 */
+	@SuppressWarnings("javadoc")
 	@FluentSetter
+	@Deprecated
 	public RestContextBuilder callHandler(Class<? extends RestCallHandler> value) {
 		return set(REST_callHandler, value);
 	}
@@ -597,19 +590,13 @@ public class RestContextBuilder extends BeanContextBuilder implements ServletCon
 	/**
 	 * <i><l>RestContext</l> configuration property:&emsp;</i>  REST call handler.
 	 *
-	 * <p>
-	 * Same as {@link #callHandler(Class)} except input is a pre-constructed instance.
-	 *
-	 * <ul class='seealso'>
-	 * 	<li class='jf'>{@link RestContext#REST_callHandler}
-	 * </ul>
-	 *
-	 * @param value
-	 * 	The new value for this setting.
-	 * 	<br>The default is {@link BasicRestCallHandler}.
-	 * @return This object (for method chaining).
+	 * <div class='warn'>
+	 * 	<b>Deprecated</b> - Use {@link RestContext#REST_context} and override methods.
+	 * </div>
 	 */
+	@SuppressWarnings("javadoc")
 	@FluentSetter
+	@Deprecated
 	public RestContextBuilder callHandler(RestCallHandler value) {
 		return set(REST_callHandler, value);
 	}
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 bf6ffed..5973e96 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
@@ -972,7 +972,7 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 				return SC_PRECONDITION_FAILED;
 		}
 
-		context.preCall(req, res);
+		context.preCall(call);
 
 		call.loggerConfig(callLoggerConfig);
 
@@ -1041,7 +1041,7 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 				}
 			}
 
-			context.postCall(req, res);
+			context.postCall(call);
 
 			if (res.hasOutput())
 				for (RestConverter converter : converters)
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 79ae60e..eae6bae 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
@@ -120,14 +120,17 @@ public final class RestRequest extends HttpServletRequestWrapper {
 	private RestResponse res;
 	private HttpPartSerializerSession partSerializerSession;
 	private HttpPartParserSession partParserSession;
+	private final RestCall call;
 
 	/**
 	 * Constructor.
 	 */
-	RestRequest(RestContext context, HttpServletRequest req) throws ServletException {
-		super(req);
+	RestRequest(RestCall call) throws ServletException {
+		super(call.getRequest());
+		HttpServletRequest req = call.getRequest();
 		this.inner = req;
-		this.context = context;
+		this.context = call.getContext();
+		this.call = call;
 
 		try {
 			isPost = req.getMethod().equalsIgnoreCase("POST");
@@ -1415,7 +1418,7 @@ public final class RestRequest extends HttpServletRequestWrapper {
 		if (varSession == null)
 			varSession = context
 				.getVarResolver()
-				.createSession(context.getCallHandler().getSessionObjects(this, context.getResponse()))
+				.createSession(context.getSessionObjects(call))
 				.sessionObject("req", this)
 				.sessionObject("res", res);
 		return varSession;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index ae31225..7642f6a 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -79,19 +79,20 @@ public final class RestResponse extends HttpServletResponseWrapper {
 	/**
 	 * Constructor.
 	 */
-	RestResponse(RestContext context, RestRequest req, HttpServletResponse res) throws BadRequest {
-		super(res);
-		this.inner = res;
-		this.request = req;
+	RestResponse(RestCall call) throws BadRequest {
+		super(call.getResponse());
+		this.inner = call.getResponse();
+		this.request = call.getRestRequest();
+		RestContext context = call.getContext();
 
 		for (Map.Entry<String,Object> e : context.getResHeaders().entrySet())
 			setHeaderSafe(e.getKey(), stringify(e.getValue()));
 
 		try {
-			String passThroughHeaders = req.getHeader("x-response-headers");
+			String passThroughHeaders = request.getHeader("x-response-headers");
 			if (passThroughHeaders != null) {
 				HttpPartParser p = context.getPartParser();
-				OMap m = p.createPartSession(req.getParserSessionArgs()).parse(HEADER, null, passThroughHeaders, context.getClassMeta(OMap.class));
+				OMap m = p.createPartSession(request.getParserSessionArgs()).parse(HEADER, null, passThroughHeaders, context.getClassMeta(OMap.class));
 				for (Map.Entry<String,Object> e : m.entrySet())
 					setHeaderSafe(e.getKey(), e.getValue().toString());
 			}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java
index 0e5074a..8ef1085 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServlet.java
@@ -42,7 +42,7 @@ import org.apache.juneau.http.exception.*;
  * 	<li class='link'>{@doc juneau-rest-server.Instantiation.RestServlet}
  * </ul>
  */
-public abstract class RestServlet extends HttpServlet implements RestCallHandler, RestInfoProvider, RestCallLogger, RestResourceResolver, ResourceFinder {
+public abstract class RestServlet extends HttpServlet implements RestInfoProvider, RestCallLogger, RestResourceResolver, ResourceFinder {
 
 	private static final long serialVersionUID = 1L;
 
@@ -52,7 +52,6 @@ public abstract class RestServlet extends HttpServlet implements RestCallHandler
 	private boolean isInitialized = false;  // Should not be volatile.
 	private volatile RestResourceResolver resourceResolver = new BasicRestResourceResolver();
 	private Logger logger = Logger.getLogger(getClass().getName());
-	private RestCallHandler callHandler;
 	private RestInfoProvider infoProvider;
 	private RestCallLogger callLogger;
 	private ResourceFinder resourceFinder;
@@ -98,7 +97,6 @@ public abstract class RestServlet extends HttpServlet implements RestCallHandler
 		this.builder = context.builder;
 		this.context = context;
 		isInitialized = true;
-		callHandler = new BasicRestCallHandler(context);
 		infoProvider = new BasicRestInfoProvider(context);
 		callLogger = new BasicRestCallLogger(context);
 		resourceFinder = new RecursiveResourceFinder();
@@ -291,7 +289,7 @@ public abstract class RestServlet extends HttpServlet implements RestCallHandler
 				isInitialized = true;
 			}
 
-			context.getCallHandler().execute(r1, r2);
+			context.execute(r1, r2);
 
 		} catch (Throwable e) {
 			r2.sendError(SC_INTERNAL_SERVER_ERROR, e.getLocalizedMessage());
@@ -600,54 +598,6 @@ public abstract class RestServlet extends HttpServlet implements RestCallHandler
 		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
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 904e97c..824351f 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
@@ -162,35 +162,12 @@ public @interface Rest {
 	/**
 	 * REST call handler.
 	 *
+	 * <div class='warn'>
+	 * 	<b>Deprecated</b> - Use {@link RestContext#REST_context} and override methods.
+	 * </div>
 	 * <p>
-	 * This class handles the basic lifecycle of an HTTP REST call.
-	 *
-	 * <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} and {@link BasicRest} classes implement 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> T <jsm>create</jsm>(RestContext)</code>
-	 * 			<li><code><jk>public static</jk> T <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_callHandler}
-	 * </ul>
 	 */
+	@Deprecated
 	Class<? extends RestCallHandler> callHandler() default RestCallHandler.Null.class;
 
 	/**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
index 690dd11..bf7e8ac 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
@@ -218,7 +218,7 @@ public class MockServletRequest implements HttpServletRequest, MockHttpRequest {
 	@Override /* MockHttpRequest */
 	public MockServletResponse execute() throws Exception {
 		MockServletResponse res = MockServletResponse.create();
-		restContext.getCallHandler().execute(this, res);
+		restContext.execute(this, res);
 
 		// If the status isn't set, something's broken.
 		if (res.getStatus() == 0)