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<{@link org.apache.juneau.rest.RestCallHandler}></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: </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: </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)