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/09/25 13:49:21 UTC

[juneau] branch master updated (0fed1f5 -> f017e67)

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

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


    from 0fed1f5  RestContext refactoring.
     new 586b71c  RestContext refactoring.
     new f017e67  REST API refactoring.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../annotation/RestMethod_ClientVersion_Test.java  |   6 +-
 .../apache/juneau/rest/BasicRestInfoProvider.java  |   2 +-
 .../main/java/org/apache/juneau/rest/RestCall.java |  22 +++
 .../java/org/apache/juneau/rest/RestContext.java   | 169 ++++++++++-----------
 .../org/apache/juneau/rest/RestMethodContext.java  |  94 +++++++++---
 .../org/apache/juneau/rest/SwaggerGenerator.java   |   6 +-
 6 files changed, 179 insertions(+), 120 deletions(-)


[juneau] 01/02: RestContext refactoring.

Posted by ja...@apache.org.
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

commit 586b71c34addade1e4c500ea0efb966aa84e1291
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Mon Aug 3 12:12:22 2020 -0400

    RestContext refactoring.
---
 .../apache/juneau/rest/BasicRestInfoProvider.java  |  2 +-
 .../java/org/apache/juneau/rest/RestContext.java   | 62 ++++++++++------------
 .../org/apache/juneau/rest/SwaggerGenerator.java   |  6 +--
 3 files changed, 32 insertions(+), 38 deletions(-)

diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
index 2d7d342..0e2cac8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestInfoProvider.java
@@ -107,7 +107,7 @@ public class BasicRestInfoProvider implements RestInfoProvider {
 		// Find it in the cache.
 		// Swaggers are cached by user locale and an int hash of the @RestMethods they have access to.
 		HashCode userHash = HashCode.create();
-		for (RestMethodContext sm : context.getCallMethods().values())
+		for (RestMethodContext sm : context.getMethodContexts())
 			if (sm.isRequestAllowed(req))
 				userHash.add(sm.hashCode());
 		int hashCode = userHash.get();
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 e6666bd..762264f 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
@@ -3598,8 +3598,8 @@ public class RestContext extends BeanContext {
 	private final Messages msgs;
 	private final Config config;
 	private final VarResolver varResolver;
-	private final Map<String,RestMethodContext[]> methodMap;
-	private final Map<String,RestMethodContext> callMethods;
+	private final Map<String,List<RestMethodContext>> methodMap;
+	private final List<RestMethodContext> methods;
 	private final Map<String,RestContext> childResources;
 	@SuppressWarnings("deprecation") private final RestLogger logger;
 	private final RestCallLogger callLogger;
@@ -3856,7 +3856,6 @@ public class RestContext extends BeanContext {
 			//----------------------------------------------------------------------------------------------------
 			List<String> methodsFound = new LinkedList<>();   // Temporary to help debug transient duplicate method issue.
 			MethodMapBuilder methodMapBuilder = new MethodMapBuilder();
-			AMap<String,RestMethodContext> _javaRestMethods = AMap.of();
 			AMap<String,Method>
 				_startCallMethods = AMap.of(),
 				_preCallMethods = AMap.of(),
@@ -3956,11 +3955,9 @@ public class RestContext extends BeanContext {
 								}
 							};
 
-							_javaRestMethods.put(mi.getSimpleName(), sm);
 							methodMapBuilder.add("GET", sm).add("POST", sm);
 
 						} else {
-							_javaRestMethods.put(mi.getSimpleName(), sm);
 							methodMapBuilder.add(httpMethod, sm);
 						}
 					} catch (Throwable e) {
@@ -4040,7 +4037,6 @@ public class RestContext extends BeanContext {
 				}
 			}
 
-			this.callMethods = _javaRestMethods.unmodifiable();
 			this.preCallMethods = _preCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_preCallMethods.size()]);
 			this.postCallMethods = _postCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_postCallMethods.size()]);
 			this.startCallMethods = _startCallMethods.values().stream().map(x->new MethodInvoker(x, getMethodExecStats(x))).collect(Collectors.toList()).toArray(new MethodInvoker[_startCallMethods.size()]);
@@ -4056,7 +4052,8 @@ public class RestContext extends BeanContext {
 			this.postInitChildFirstMethodParams = _postInitChildFirstMethodParams.toArray(new Class[_postInitChildFirstMethodParams.size()][]);
 			this.destroyMethodParams = _destroyMethodParams.toArray(new Class[_destroyMethodParams.size()][]);
 
-			this.methodMap = methodMapBuilder.build();
+			this.methodMap = methodMapBuilder.getMap();
+			this.methods = methodMapBuilder.getList();
 
 			// Initialize our child resources.
 			for (Object o : getArrayProperty(REST_children, Object.class)) {
@@ -4112,23 +4109,30 @@ public class RestContext extends BeanContext {
 		}
 	}
 
-	static class MethodMapBuilder extends TreeMap<String,TreeSet<RestMethodContext>> {
-		private static final long serialVersionUID = 1L;
+	static class MethodMapBuilder  {
+		TreeMap<String,TreeSet<RestMethodContext>> map = new TreeMap<>();
+		Set<RestMethodContext> list = ASet.of();
+
 
 		MethodMapBuilder add(String httpMethodName, RestMethodContext mc) {
 			httpMethodName = httpMethodName.toUpperCase();
-			if (! containsKey(httpMethodName))
-				put(httpMethodName, new TreeSet<>());
-			get(httpMethodName).add(mc);
+			if (! map.containsKey(httpMethodName))
+				map.put(httpMethodName, new TreeSet<>());
+			map.get(httpMethodName).add(mc);
+			list.add(mc);
 			return this;
 		}
 
-		Map<String,RestMethodContext[]> build() {
-			Map<String,RestMethodContext[]> m = new LinkedHashMap<>();
-			for (Map.Entry<String,TreeSet<RestMethodContext>> e : this.entrySet())
-				m.put(e.getKey(), e.getValue().toArray(new RestMethodContext[0]));
+		Map<String,List<RestMethodContext>> getMap() {
+			Map<String,List<RestMethodContext>> m = new LinkedHashMap<>();
+			for (Map.Entry<String,TreeSet<RestMethodContext>> e : map.entrySet())
+				m.put(e.getKey(), AList.of(e.getValue()));
 			return Collections.unmodifiableMap(m);
 		}
+
+		List<RestMethodContext> getList() {
+			return AList.of(list);
+		}
 	}
 
 	/**
@@ -4929,16 +4933,6 @@ public class RestContext extends BeanContext {
 	}
 
 	/**
-	 * Returns the parameters defined on the specified Java method.
-	 *
-	 * @param method The Java method to check.
-	 * @return The parameters defined on the Java method.
-	 */
-	public RestMethodParam[] getRestMethodParams(Method method) {
-		return callMethods.get(method.getName()).methodParams;
-	}
-
-	/**
 	 * Returns the media type for the specified file name.
 	 *
 	 * <ul class='seealso'>
@@ -4978,8 +4972,8 @@ public class RestContext extends BeanContext {
 	 * @return
 	 * 	An unmodifiable map of Java method names to call method objects.
 	 */
-	public Map<String,RestMethodContext> getCallMethods() {
-		return callMethods;
+	public List<RestMethodContext> getMethodContexts() {
+		return methods;
 	}
 
 	/**
@@ -5272,14 +5266,14 @@ public class RestContext extends BeanContext {
 
 		// Should be 405 if the URL pattern matched but HTTP method did not.
 		if (rc == 0)
-			for (RestMethodContext[] rcc : methodMap.values())
+			for (List<RestMethodContext> rcc : methodMap.values())
 				if (matches(rcc, call))
 					rc = SC_METHOD_NOT_ALLOWED;
 
 		return rc;
 	}
 
-	private boolean matches(RestMethodContext[] mc, RestCall call) throws Throwable {
+	private boolean matches(List<RestMethodContext> mc, RestCall call) throws Throwable {
 		UrlPathInfo pi = call.getUrlPathInfo();
 		for (RestMethodContext m : mc)
 			if (m.matches(pi))
@@ -5287,10 +5281,10 @@ public class RestContext extends BeanContext {
 		return false;
 	}
 
-	private int invoke(RestMethodContext[] mc, RestCall call) throws Throwable {
-		if (mc.length == 1) {
-			call.restMethodContext(mc[0]);
-			return mc[0].invoke(call);
+	private int invoke(List<RestMethodContext> mc, RestCall call) throws Throwable {
+		if (mc.size() == 1) {
+			call.restMethodContext(mc.get(0));
+			return mc.get(0).invoke(call);
 		}
 
 		int maxRc = 0;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java
index 0aef1cb..f542985 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/SwaggerGenerator.java
@@ -285,7 +285,7 @@ final class SwaggerGenerator {
 			js.addBeanDef(defId, new OMap(definitions.getMap(defId)));
 
 		// Iterate through all the @RestMethod methods.
-		for (RestMethodContext sm : context.getCallMethods().values()) {
+		for (RestMethodContext sm : context.getMethodContexts()) {
 
 			BeanSession bs = sm.createBeanSession();
 
@@ -385,7 +385,7 @@ final class SwaggerGenerator {
 					paramMap.put(param.getString("in") + '.' + ("body".equals(param.getString("in")) ? "body" : param.getString("name")), param);
 
 			// Finally, look for parameters defined on method.
-			for (RestMethodParam mp : context.getRestMethodParams(m)) {
+			for (RestMethodParam mp : sm.methodParams) {
 
 				RestParamType in = mp.getParamType();
 				ParamInfo mpi = mp.getMethodParamInfo();
@@ -506,7 +506,7 @@ final class SwaggerGenerator {
 			}
 
 			// Finally, look for @ResponseHeader parameters defined on method.
-			for (RestMethodParam mp : context.getRestMethodParams(m)) {
+			for (RestMethodParam mp : sm.methodParams) {
 
 				RestParamType in = mp.getParamType();
 				ParamInfo mpi = mp.getMethodParamInfo();


[juneau] 02/02: REST API refactoring.

Posted by ja...@apache.org.
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

commit f017e678a31da6f2a7aa20e52640612d1e7ac788
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Fri Sep 25 09:49:17 2020 -0400

    REST API refactoring.
---
 .../annotation/RestMethod_ClientVersion_Test.java  |   6 +-
 .../main/java/org/apache/juneau/rest/RestCall.java |  22 +++
 .../java/org/apache/juneau/rest/RestContext.java   | 151 ++++++++++-----------
 .../org/apache/juneau/rest/RestMethodContext.java  |  94 ++++++++++---
 4 files changed, 169 insertions(+), 104 deletions(-)

diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethod_ClientVersion_Test.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethod_ClientVersion_Test.java
index 6d2b052..fac19aa 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethod_ClientVersion_Test.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethod_ClientVersion_Test.java
@@ -55,13 +55,13 @@ public class RestMethod_ClientVersion_Test {
 		RestClient a = MockRestClient.build(A.class);
 		a.get("/").run().assertBody().is("no-version");
 		for (String s : "1, 1.0, 1.0.0, 1.0.1".split("\\s*,\\s*")) {
-			a.get("/").clientVersion(s).run().assertBody().is("[1.0,1.0]");
+			a.get("/").clientVersion(s).run().assertBody().msg("s=[{0}]",s).is("[1.0,1.0]");
 		}
 		for (String s : "1.1, 1.1.1, 1.2, 1.9.9".split("\\s*,\\s*")) {
-			a.get("/").clientVersion(s).run().assertBody().is("[1.1,2)");
+			a.get("/").clientVersion(s).run().assertBody().msg("s=[{0}]").is("[1.1,2)");
 		}
 		for (String s : "2, 2.0, 2.1, 9, 9.9".split("\\s*,\\s*")) {
-			a.get("/").clientVersion(s).run().assertBody().is("2");
+			a.get("/").clientVersion(s).run().assertBody().msg("s=[{0}]").is("2");
 		}
 	}
 
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 fc79cad..aa034d6 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
@@ -43,6 +43,8 @@ public class RestCall {
 	private RestCallLogger logger;
 	private RestCallLoggerConfig loggerConfig;
 
+	private UrlPathPatternMatch urlPathPatternMatch;
+
 	/**
 	 * Constructor.
 	 *
@@ -303,6 +305,26 @@ public class RestCall {
 		return this;
 	}
 
+	/**
+	 * Sets the URL path pattern match on this call.
+	 *
+	 * @param urlPathPatternMatch The match pattern.
+	 * @return This object (for method chaining).
+	 */
+	public RestCall urlPathPatternMatch(UrlPathPatternMatch urlPathPatternMatch) {
+		this.urlPathPatternMatch = urlPathPatternMatch;
+		return this;
+	}
+
+	/**
+	 * Returns the URL path pattern match on this call.
+	 *
+	 * @return The URL path pattern match on this call.
+	 */
+	public UrlPathPatternMatch getUrlPathPatternMatch() {
+		return urlPathPatternMatch;
+	}
+
 	//------------------------------------------------------------------------------------------------------------------
 	// Lifecycle methods.
 	//------------------------------------------------------------------------------------------------------------------
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 762264f..582c481 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
@@ -3911,17 +3911,15 @@ public class RestContext extends BeanContext {
 							sm = new RestMethodContext(smb) {
 
 								@Override
-								int invoke(RestCall call) throws Throwable {
+								void invoke(RestCall call) throws Throwable {
 
-									int rc = super.invoke(call);
-									if (rc != SC_OK)
-										return rc;
+									super.invoke(call);
 
 									final Object o = call.getOutput();
 
 									if ("GET".equals(call.getMethod())) {
 										call.output(rim.getMethodsByPath().keySet());
-										return SC_OK;
+										return;
 
 									} else if ("POST".equals(call.getMethod())) {
 										String pip = call.getUrlPathInfo().getPath();
@@ -3945,13 +3943,13 @@ public class RestContext extends BeanContext {
 												}
 												Object output = m.invoke(o, args);
 												call.output(output);
-												return SC_OK;
+												return;
 											} catch (Exception e) {
 												throw toHttpException(e, InternalServerError.class);
 											}
 										}
 									}
-									return SC_NOT_FOUND;
+									throw new NotFound();
 								}
 							};
 
@@ -4109,32 +4107,6 @@ public class RestContext extends BeanContext {
 		}
 	}
 
-	static class MethodMapBuilder  {
-		TreeMap<String,TreeSet<RestMethodContext>> map = new TreeMap<>();
-		Set<RestMethodContext> list = ASet.of();
-
-
-		MethodMapBuilder add(String httpMethodName, RestMethodContext mc) {
-			httpMethodName = httpMethodName.toUpperCase();
-			if (! map.containsKey(httpMethodName))
-				map.put(httpMethodName, new TreeSet<>());
-			map.get(httpMethodName).add(mc);
-			list.add(mc);
-			return this;
-		}
-
-		Map<String,List<RestMethodContext>> getMap() {
-			Map<String,List<RestMethodContext>> m = new LinkedHashMap<>();
-			for (Map.Entry<String,TreeSet<RestMethodContext>> e : map.entrySet())
-				m.put(e.getKey(), AList.of(e.getValue()));
-			return Collections.unmodifiableMap(m);
-		}
-
-		List<RestMethodContext> getList() {
-			return AList.of(list);
-		}
-	}
-
 	/**
 	 * Returns the resource resolver associated with this context.
 	 *
@@ -5223,18 +5195,13 @@ public class RestContext extends BeanContext {
 			} else {
 
 				// If the specified method has been defined in a subclass, invoke it.
-				int rc = findMethodAndInvoke(call);
-
-				// 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);
+				try {
+					findMethod(call).invoke(call);
+				} catch (NotFound e) {
+					if (call.getStatus() == 0)
+						call.status(404);
+					handleNotFound(call);
+				}
 			}
 
 			if (call.hasOutput()) {
@@ -5254,50 +5221,44 @@ public class RestContext extends BeanContext {
 		finishCall(call);
 	}
 
-	private int findMethodAndInvoke(RestCall call) throws Throwable {
+	private RestMethodContext findMethod(RestCall call) throws Throwable {
 		String m = call.getMethod();
 
 		int rc = 0;
-		if (methodMap.containsKey(m))
-			rc = invoke(methodMap.get(m), call);
-
-		if ((rc == 0 || rc == 404) && methodMap.containsKey("*"))
-			rc = invoke(methodMap.get("*"), call);
-
-		// Should be 405 if the URL pattern matched but HTTP method did not.
-		if (rc == 0)
-			for (List<RestMethodContext> rcc : methodMap.values())
-				if (matches(rcc, call))
-					rc = SC_METHOD_NOT_ALLOWED;
-
-		return rc;
-	}
-
-	private boolean matches(List<RestMethodContext> mc, RestCall call) throws Throwable {
-		UrlPathInfo pi = call.getUrlPathInfo();
-		for (RestMethodContext m : mc)
-			if (m.matches(pi))
-				return true;
-		return false;
-	}
+		if (methodMap.containsKey(m)) {
+			for (RestMethodContext mc : methodMap.get(m)) {
+				int mrc = mc.match(call);
+				if (mrc == 2)
+					return mc;
+				rc = Math.max(rc, mrc);
+			}
+		}
 
-	private int invoke(List<RestMethodContext> mc, RestCall call) throws Throwable {
-		if (mc.size() == 1) {
-			call.restMethodContext(mc.get(0));
-			return mc.get(0).invoke(call);
+		if (methodMap.containsKey("*")) {
+			for (RestMethodContext mc : methodMap.get("*")) {
+				int mrc = mc.match(call);
+				if (mrc == 2)
+					return mc;
+				rc = Math.max(rc, mrc);
+			}
 		}
 
-		int maxRc = 0;
-		for (RestMethodContext m : mc) {
-			int rc = m.invoke(call);
-			if (rc == SC_OK) {
-				call.restMethodContext(m);
-				return SC_OK;
+		// If no paths matched, see if the path matches any other methods.
+		// Note that we don't want to match against "/*" patterns such as getOptions().
+		if (rc == 0) {
+			for (RestMethodContext mc : methods) {
+				if (! mc.getPathPattern().endsWith("/*")) {
+					int mrc = mc.match(call);
+					if (mrc == 2)
+						throw new MethodNotAllowed();
+				}
 			}
-			maxRc = Math.max(maxRc, rc);
 		}
-		return maxRc;
 
+		if (rc == 1)
+			throw new PreconditionFailed("Method ''{0}'' not found on resource on path ''{1}'' with matching matcher.", m, call.getPathInfo());
+
+		throw new NotFound();
 	}
 
 	private boolean isDebug(RestCall call) {
@@ -5743,4 +5704,34 @@ public class RestContext extends BeanContext {
 				.a("useClasspathResourceCaching", useClasspathResourceCaching)
 			);
 	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helpers.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	static class MethodMapBuilder  {
+		TreeMap<String,TreeSet<RestMethodContext>> map = new TreeMap<>();
+		Set<RestMethodContext> set = ASet.of();
+
+
+		MethodMapBuilder add(String httpMethodName, RestMethodContext mc) {
+			httpMethodName = httpMethodName.toUpperCase();
+			if (! map.containsKey(httpMethodName))
+				map.put(httpMethodName, new TreeSet<>());
+			map.get(httpMethodName).add(mc);
+			set.add(mc);
+			return this;
+		}
+
+		Map<String,List<RestMethodContext>> getMap() {
+			AMap<String,List<RestMethodContext>> m = AMap.of();
+			for (Map.Entry<String,TreeSet<RestMethodContext>> e : map.entrySet())
+				m.put(e.getKey(), AList.of(e.getValue()));
+			return m.unmodifiable();
+		}
+
+		List<RestMethodContext> getList() {
+			return AList.of(set).unmodifiable();
+		}
+	}
 }
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 25b20cc..f980e58 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
@@ -13,7 +13,6 @@
 package org.apache.juneau.rest;
 
 import static org.apache.juneau.rest.Enablement.*;
-import static javax.servlet.http.HttpServletResponse.*;
 import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.ObjectUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
@@ -929,21 +928,87 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	}
 
 	/**
-	 * Workhorse method.
+	 * Identifies if this method can process the specified call.
 	 *
-	 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
-	 * @return The HTTP response code.
+	 * <p>
+	 * To process the call, the following must be true:
+	 * <ul>
+	 * 	<li>Path pattern must match.
+	 * 	<li>Matchers (if any) must match.
+	 * </ul>
+	 *
+	 * @param call The call to check.
+	 * @return
+	 * 	One of the following values:
+	 * 	<ul>
+	 * 		<li><c>0</c> - Path doesn't match.
+	 * 		<li><c>1</c> - Path matched but matchers did not.
+	 * 		<li><c>2</c> - Matches.
+	 * 	</ul>
 	 */
-	int invoke(RestCall call) throws Throwable {
+	protected int match(RestCall call) {
 
-		UrlPathPatternMatch pm = null;
+		UrlPathPatternMatch pm = matchPattern(call);
+
+		if (pm == null)
+			return 0;
+
+		if (requiredMatchers.length == 0 && optionalMatchers.length == 0) {
+			call.urlPathPatternMatch(pm);  // Cache so we don't have to recalculate.
+			return 2;
+		}
+
+		try {
+			RestRequest req = call.getRestRequest();
+			RestResponse res = call.getRestResponse();
+
+			@SuppressWarnings("deprecation")
+			RequestProperties requestProperties = new RequestProperties(req.getVarResolverSession(), properties);
+
+			req.init(this, requestProperties);
+			res.init(this, requestProperties);
+
+			// If the method implements matchers, test them.
+			for (RestMatcher m : requiredMatchers)
+				if (! m.matches(req))
+					return 1;
+			if (optionalMatchers.length > 0) {
+				boolean matches = false;
+				for (RestMatcher m : optionalMatchers)
+					matches |= m.matches(req);
+				if (! matches)
+					return 1;
+			}
 
+			call.urlPathPatternMatch(pm);  // Cache so we don't have to recalculate.
+			return 2;
+		} catch (Exception e) {
+			throw new InternalServerError(e);
+		}
+	}
+
+	private UrlPathPatternMatch matchPattern(RestCall call) {
+		UrlPathPatternMatch pm = null;
 		for (UrlPathPattern pp : pathPatterns)
 			if (pm == null)
 				pm = pp.match(call.getUrlPathInfo());
+		return pm;
+	}
 
+
+	/**
+	 * Workhorse method.
+	 *
+	 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta)
+	 */
+	void invoke(RestCall call) throws Throwable {
+
+		UrlPathPatternMatch pm = call.getUrlPathPatternMatch();
 		if (pm == null)
-			return SC_NOT_FOUND;
+			pm = matchPattern(call);
+
+		if (pm == null)
+			throw new NotFound();
 
 		RestRequest req = call.getRestRequest();
 		RestResponse res = call.getRestResponse();
@@ -960,18 +1025,6 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 		req.init(this, requestProperties);
 		res.init(this, requestProperties);
 
-		// If the method implements matchers, test them.
-		for (RestMatcher m : requiredMatchers)
-			if (! m.matches(req))
-				return SC_PRECONDITION_FAILED;
-		if (optionalMatchers.length > 0) {
-			boolean matches = false;
-			for (RestMatcher m : optionalMatchers)
-				matches |= m.matches(req);
-			if (! matches)
-				return SC_PRECONDITION_FAILED;
-		}
-
 		context.preCall(call);
 
 		call.loggerConfig(callLoggerConfig);
@@ -1006,7 +1059,7 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 
 			for (RestGuard guard : guards)
 				if (! guard.guard(req, res))
-					return SC_OK;
+					return;
 
 			Object output;
 			try {
@@ -1055,7 +1108,6 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 		} catch (InvocationTargetException e) {
 			throw e.getTargetException();
 		}
-		return SC_OK;
 	}
 //
 //	protected void addStatusCode(int code) {