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 bc3b4dd REST refactoring.
bc3b4dd is described below
commit bc3b4dde6f66aa12fd403ac0c0cd8cf45bd32235
Author: JamesBognar <[email protected]>
AuthorDate: Thu Jan 21 15:32:56 2021 -0500
REST refactoring.
---
.../src/main/java/org/apache/juneau/Context.java | 2 +-
.../juneau/rest/RestContext_ThreadLocals_Test.java | 10 +-
.../java/org/apache/juneau/rest/RestContext.java | 228 ++++++---------------
.../org/apache/juneau/rest/RestMethodContext.java | 34 +--
.../juneau/rest/RestMethodContextBuilder.java | 21 +-
.../java/org/apache/juneau/rest/RestMethods.java | 104 ++++++++++
.../org/apache/juneau/rest/RestMethodsBuilder.java | 61 ++++++
.../apache/juneau/rest/RrpcRestMethodContext.java | 92 +++++++++
8 files changed, 356 insertions(+), 196 deletions(-)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
index e5fcb75..d86e6b1 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
@@ -931,7 +931,7 @@ public abstract class Context {
return new DefaultFilteringOMap()
.a("Context", new DefaultFilteringOMap()
.a("identityCode", identityCode)
- .a("propertyStore", propertyStore)
+ .a("propertyStore",
System.identityHashCode(propertyStore))
);
}
}
diff --git
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/RestContext_ThreadLocals_Test.java
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/RestContext_ThreadLocals_Test.java
index 7d6bd0f..3e815ef 100644
---
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/RestContext_ThreadLocals_Test.java
+++
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/RestContext_ThreadLocals_Test.java
@@ -12,7 +12,7 @@
//
***************************************************************************************************************************
package org.apache.juneau.rest;
-import static org.junit.Assert.*;
+import static org.apache.juneau.assertions.Assertions.*;
import static org.junit.runners.MethodSorters.*;
import org.apache.juneau.rest.annotation.*;
@@ -36,8 +36,8 @@ public class RestContext_ThreadLocals_Test {
@RestHook(HookEvent.END_CALL)
public void assertThreadsNotSet() {
- assertNull(getRequest());
- assertNull(getResponse());
+ assertThrown(()->getRequest()).contains("No active
request on current thread.");
+ assertThrown(()->getResponse()).contains("No active
request on current thread.");
}
}
static MockRestClient a = MockRestClient.build(A.class);
@@ -62,8 +62,8 @@ public class RestContext_ThreadLocals_Test {
public static class B extends BasicRestServletGroup {
@RestHook(HookEvent.END_CALL)
public void assertThreadsNotSet2() {
- assertNull(getRequest());
- assertNull(getResponse());
+ assertThrown(()->getRequest()).contains("No active
request on current thread.");
+ assertThrown(()->getResponse()).contains("No active
request on current thread.");
}
}
static MockRestClient b = MockRestClient.build(B.class);
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 5f0cef5..4863d25 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
@@ -17,7 +17,6 @@ import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static org.apache.juneau.internal.IOUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
-import static org.apache.juneau.rest.util.RestUtils.*;
import static org.apache.juneau.rest.HttpRuntimeException.*;
import static org.apache.juneau.Enablement.*;
import static java.util.Collections.*;
@@ -64,7 +63,6 @@ import org.apache.juneau.rest.converters.*;
import org.apache.juneau.rest.logging.*;
import org.apache.juneau.rest.params.*;
import org.apache.juneau.http.exception.*;
-import org.apache.juneau.http.remote.*;
import org.apache.juneau.rest.reshandlers.*;
import org.apache.juneau.rest.util.*;
import org.apache.juneau.rest.vars.*;
@@ -3184,8 +3182,7 @@ public class RestContext extends BeanContext {
private final Messages msgs;
private final Config config;
private final VarResolver varResolver;
- private final Map<String,List<RestMethodContext>> methodMap;
- private final List<RestMethodContext> methods;
+ private final RestMethods restMethods;
private final Map<String,RestContext> childResources;
private final StackTraceStore stackTraceStore;
private final Logger logger;
@@ -3366,105 +3363,8 @@ public class RestContext extends BeanContext {
preCallMethods =
createPreCallMethods(r).stream().map(this::toRestMethodInvoker).toArray(RestMethodInvoker[]::
new);
postCallMethods =
createPostCallMethods(r).stream().map(this::toRestMethodInvoker).toArray(RestMethodInvoker[]::
new);
-
//----------------------------------------------------------------------------------------------------
- // Initialize the child resources.
- // Done after initializing fields above since we pass
this object to the child resources.
-
//----------------------------------------------------------------------------------------------------
- List<String> methodsFound = new LinkedList<>(); //
Temporary to help debug transient duplicate method issue.
- MethodMapBuilder methodMapBuilder = new
MethodMapBuilder();
-
- for (MethodInfo mi : rci.getPublicMethods()) {
- RestMethod a =
mi.getLastAnnotation(RestMethod.class);
-
- // Also include methods on @Rest-annotated
interfaces.
- if (a == null) {
- for (Method mi2 : mi.getMatching()) {
- Class<?> ci2 =
mi2.getDeclaringClass();
- if (ci2.isInterface() &&
ci2.getAnnotation(Rest.class) != null) {
- a =
RestMethodAnnotation.DEFAULT;
- }
- }
- }
- if (a != null) {
- methodsFound.add(mi.getSimpleName() +
"," + emptyIfNull(a.method()) + "," + fixMethodPath(a.path().length > 0 ?
a.path()[0] : ""));
- try {
- if (mi.isNotPublic())
- throw new
RestServletException("@RestMethod method {0}.{1} must be defined as public.",
rci.inner().getName(), mi.getSimpleName());
-
- RestMethodContextBuilder rmcb =
new RestMethodContextBuilder(r, mi.inner(), this);
- RestMethodContext sm = new
RestMethodContext(rmcb);
- String httpMethod =
sm.getHttpMethod();
-
- // RRPC is a special case where
a method returns an interface that we
- // can perform REST calls
against.
- // We override the
CallMethod.invoke() method to insert our logic.
- if ("RRPC".equals(httpMethod)) {
-
- final ClassMeta<?>
interfaceClass = getClassMeta(mi.inner().getGenericReturnType());
- final RrpcInterfaceMeta
rim = new RrpcInterfaceMeta(interfaceClass.getInnerClass(), null);
- if
(rim.getMethodsByPath().isEmpty())
- throw new
InternalServerError("Method {0} returns an interface {1} that doesn't define
any remote methods.", mi.getSignature(), interfaceClass.getFullName());
-
-
RestMethodContextBuilder smb = new RestMethodContextBuilder(r, mi.inner(),
this);
- smb.dotAll();
- sm = new
RestMethodContext(smb) {
-
- @Override
- void
invoke(RestCall call) throws Throwable {
-
-
super.invoke(call);
-
- final
Object o = call.getOutput();
-
- if
("GET".equals(call.getMethod())) {
-
call.output(rim.getMethodsByPath().keySet());
-
return;
-
- } else
if ("POST".equals(call.getMethod())) {
-
String pip = call.getUrlPath().getPath();
-
if (pip.indexOf('/') != -1)
-
pip = pip.substring(pip.lastIndexOf('/')+1);
-
pip = urlDecode(pip);
-
RrpcInterfaceMethodMeta rmm = rim.getMethodMetaByPath(pip);
-
if (rmm != null) {
-
Method m = rmm.getJavaMethod();
-
try {
-
RestRequest req = call.getRestRequest();
-
// Parse the args and invoke the method.
-
Parser p = req.getBody().getParser();
-
Object[] args = null;
-
if (m.getGenericParameterTypes().length == 0)
-
args = new Object[0];
-
else {
-
try (Closeable in = p.isReaderParser() ?
req.getReader() : req.getInputStream()) {
-
args = p.parseArgs(in,
m.getGenericParameterTypes());
-
}
-
}
-
Object output = m.invoke(o, args);
-
call.output(output);
-
return;
-
} catch (Exception e) {
-
throw toHttpException(e, InternalServerError.class);
-
}
-
}
- }
- throw
new NotFound();
- }
- };
-
-
methodMapBuilder.add("GET", sm).add("POST", sm);
+ restMethods = createRestMethods(r).build();
- } else {
-
methodMapBuilder.add(httpMethod, sm);
- }
- } catch (Throwable e) {
- throw new
RestServletException(e, "Problem occurred trying to initialize methods on class
{0}, methods={1}", rci.inner().getName(),
SimpleJsonSerializer.DEFAULT.serialize(methodsFound));
- }
- }
- }
-
- this.methodMap = methodMapBuilder.getMap();
- this.methods = methodMapBuilder.getList();
// Initialize our child resources.
for (Object o : getArrayProperty(REST_children,
Object.class)) {
@@ -4648,6 +4548,61 @@ public class RestContext extends BeanContext {
}
/**
+ * Creates the set of {@link RestMethodContext} objects that represent
the methods on this resource.
+ *
+ * @param resource The REST resource object.
+ * @return The builder for the {@link RestMethods} object.
+ * @throws Exception An error occurred.
+ */
+ protected RestMethodsBuilder createRestMethods(Object resource) throws
Exception {
+ RestMethodsBuilder x = new RestMethodsBuilder();
+ ClassInfo rci = ClassInfo.of(resource);
+
+ for (MethodInfo mi : rci.getPublicMethods()) {
+ RestMethod a = mi.getLastAnnotation(RestMethod.class);
+
+ // Also include methods on @Rest-annotated interfaces.
+ if (a == null) {
+ for (Method mi2 : mi.getMatching()) {
+ Class<?> ci2 = mi2.getDeclaringClass();
+ if (ci2.isInterface() &&
ci2.getAnnotation(Rest.class) != null) {
+ a =
RestMethodAnnotation.DEFAULT;
+ }
+ }
+ }
+ if (a != null) {
+ try {
+ if (mi.isNotPublic())
+ throw new
RestServletException("@RestMethod method {0}.{1} must be defined as public.",
rci.inner().getName(), mi.getSimpleName());
+
+ RestMethodContextBuilder rmcb = new
RestMethodContextBuilder(resource, mi.inner(), this);
+ RestMethodContext rmc = rmcb.build();
+ String httpMethod = rmc.getHttpMethod();
+
+ // RRPC is a special case where a
method returns an interface that we
+ // can perform REST calls against.
+ // We override the CallMethod.invoke()
method to insert our logic.
+ if ("RRPC".equals(httpMethod)) {
+
+ RestMethodContextBuilder smb =
new RestMethodContextBuilder(resource, mi.inner(), this);
+ smb.dotAll();
+ x
+ .add("GET",
smb.build(RrpcRestMethodContext.class))
+ .add("POST",
smb.build(RrpcRestMethodContext.class));
+
+ } else {
+ x.add(rmc);
+ }
+ } catch (Throwable e) {
+ throw new RestServletException(e,
"Problem occurred trying to initialize methods on class {0}",
rci.inner().getName());
+ }
+ }
+ }
+
+ return x;
+ }
+
+ /**
* Instantiates the list of {@link HookEvent#START_CALL} methods.
*
* @param resource The REST resource object.
@@ -5341,7 +5296,7 @@ public class RestContext extends BeanContext {
* An unmodifiable map of Java method names to call method objects.
*/
public List<RestMethodContext> getMethodContexts() {
- return methods;
+ return restMethods.getMethodContexts();
}
/**
@@ -5572,7 +5527,7 @@ public class RestContext extends BeanContext {
// If the specified method has been defined in a
subclass, invoke it.
try {
- findMethod(call).invoke(call);
+ restMethods.findMethod(call).invoke(call);
} catch (NotFound e) {
if (call.getStatus() == 0)
call.status(404);
@@ -5597,46 +5552,6 @@ public class RestContext extends BeanContext {
finishCall(call);
}
- private RestMethodContext findMethod(RestCall call) throws Throwable {
- String m = call.getMethod();
-
- int rc = 0;
- 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);
- }
- }
-
- if (methodMap.containsKey("*")) {
- for (RestMethodContext mc : methodMap.get("*")) {
- int mrc = mc.match(call);
- if (mrc == 2)
- return mc;
- rc = Math.max(rc, mrc);
- }
- }
-
- // 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();
- }
- }
- }
-
- if (rc == 1)
- throw new PreconditionFailed("Method ''{0}'' not found
on resource on path ''{1}'' with matching matcher.", m, call.getPathInfo());
-
- throw new NotFound("Java method matching path ''{0}'' not found
on resource ''{1}''.", call.getPathInfo(), getResource().getClass().getName());
- }
-
private boolean isDebug(RestCall call) {
Enablement e = null;
RestMethodContext mc = call.getRestMethodContext();
@@ -6071,29 +5986,4 @@ public class RestContext extends BeanContext {
// 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.create();
- 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 c6e6681..0a68e4d 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
@@ -30,7 +30,6 @@ import java.util.concurrent.atomic.*;
import java.util.function.*;
import javax.servlet.*;
-import javax.servlet.http.*;
import org.apache.http.*;
import org.apache.http.ParseException;
@@ -636,15 +635,22 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
final Enablement debug;
final int hierarchyDepth;
- RestMethodContext(RestMethodContextBuilder b) throws ServletException {
- super(b.getPropertyStore());
+ /**
+ * Context constructor.
+ *
+ * @param ps The property store with settings.
+ * @throws ServletException If context could not be created.
+ */
+ public RestMethodContext(PropertyStore ps) throws ServletException {
+ super(ps);
try {
- context = b.context;
- method = b.method;
+ context =
getInstanceProperty("RestMethodContext.restContext.o", RestContext.class);
+ method =
getInstanceProperty("RestMethodContext.restMethod.o", Method.class);
+ boolean dotAll =
getBooleanProperty("RestMethodContext.dotAll.b", false);
+
methodInvoker = new MethodInvoker(method,
context.getMethodExecStats(method));
mi = MethodInfo.of(method).accessible();
- PropertyStore ps = getPropertyStore();
Object r = context.getResource();
beanFactory = new BeanFactory(context.rootBeanFactory,
r)
@@ -675,7 +681,7 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
requiredMatchers = matchers.stream().filter(x ->
x.required()).toArray(RestMatcher[]::new);
optionalMatchers = matchers.stream().filter(x -> !
x.required()).toArray(RestMatcher[]::new);
- pathMatchers = createPathMatchers(r, beanFactory,
b.dotAll).asArray();
+ pathMatchers = createPathMatchers(r, beanFactory,
dotAll).asArray();
beanFactory.addBean(UrlPathMatcher[].class,
pathMatchers);
beanFactory.addBean(UrlPathMatcher.class,
pathMatchers.length > 0 ? pathMatchers[0] : null);
@@ -695,7 +701,7 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
defaultRequestAttributes =
createDefaultRequestAttributes(r, beanFactory, method, context).asArray();
int _hierarchyDepth = 0;
- Class<?> sc =
b.method.getDeclaringClass().getSuperclass();
+ Class<?> sc =
method.getDeclaringClass().getSuperclass();
while (sc != null) {
_hierarchyDepth++;
sc = sc.getSuperclass();
@@ -1299,7 +1305,7 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
HeaderList x = HeaderList.create();
x.appendUnique(context.defaultRequestHeaders);
-
+
x.appendUnique(getInstanceArrayProperty(RESTMETHOD_defaultRequestHeaders,
org.apache.http.Header.class, new org.apache.http.Header[0], beanFactory));
for (Annotation[] aa : method.getParameterAnnotations()) {
@@ -1344,7 +1350,7 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
HeaderList x = HeaderList.create();
x.appendUnique(context.defaultResponseHeaders);
-
+
x.appendUnique(getInstanceArrayProperty(RESTMETHOD_defaultResponseHeaders,
org.apache.http.Header.class, new org.apache.http.Header[0], beanFactory));
x = BeanFactory
@@ -1372,7 +1378,7 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
NamedAttributeList x = NamedAttributeList.create();
x.appendUnique(context.defaultRequestAttributes);
-
+
x.appendUnique(getInstanceArrayProperty(RESTMETHOD_defaultRequestAttributes,
NamedAttribute.class, new NamedAttribute[0], beanFactory));
x = BeanFactory
@@ -1619,13 +1625,13 @@ public class RestMethodContext extends BeanContext
implements Comparable<RestMet
return pm;
}
-
/**
* Workhorse method.
*
- * @param pathInfo The value of {@link
HttpServletRequest#getPathInfo()} (sorta)
+ * @param call Invokes the specified call against this Java method.
+ * @throws Throwable Typically an HTTP exception. Anything else will
result in an HTTP 500.
*/
- void invoke(RestCall call) throws Throwable {
+ protected void invoke(RestCall call) throws Throwable {
UrlPathMatch pm = call.getUrlPathMatch();
if (pm == null)
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
index 24936ec..bd3dbda 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContextBuilder.java
@@ -19,6 +19,8 @@ import java.lang.annotation.*;
import java.util.*;
import java.util.function.*;
+import javax.servlet.*;
+
import org.apache.http.*;
import org.apache.juneau.*;
import org.apache.juneau.http.*;
@@ -33,14 +35,19 @@ import java.lang.reflect.Method;
*/
public class RestMethodContextBuilder extends BeanContextBuilder {
- RestContext context;
- java.lang.reflect.Method method;
-
- boolean dotAll;
+ @Override
+ public RestMethodContext build() {
+ try {
+ return new RestMethodContext(getPropertyStore());
+ } catch (ServletException e) {
+ throw new RuntimeException(e);
+ }
+ }
RestMethodContextBuilder(Object servlet, java.lang.reflect.Method
method, RestContext context) throws RestServletException {
- this.context = context;
- this.method = method;
+ set("RestMethodContext.restContext.o", context);
+ set("RestMethodContext.restMethod.o", method);
+ set("RestMethodContext.restObject.o", context.getResource());
// Added to force a new cache hash.
String sig = method.getDeclaringClass().getName() + '.' +
method.getName();
MethodInfo mi = MethodInfo.of(servlet.getClass(), method);
@@ -79,7 +86,7 @@ public class RestMethodContextBuilder extends
BeanContextBuilder {
* @return This object (for method chaining).
*/
public RestMethodContextBuilder dotAll() {
- this.dotAll = true;
+ set("RestMethodContext.dotAll.b", true);
return this;
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java
new file mode 100644
index 0000000..2690e28
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethods.java
@@ -0,0 +1,104 @@
+//
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file *
+// * distributed with this work for additional information regarding copyright
ownership. The ASF licenses this file *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance *
+// * with the License. You may obtain a copy of the License at
*
+// *
*
+// * http://www.apache.org/licenses/LICENSE-2.0
*
+// *
*
+// * Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the *
+// * specific language governing permissions and limitations under the
License. *
+//
***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import java.util.*;
+
+import org.apache.juneau.collections.*;
+import org.apache.juneau.http.exception.*;
+import org.apache.juneau.rest.annotation.*;
+
+/**
+ * Encapsulates the set of {@link RestMethod}-annotated methods within a
single {@link Rest}-annotated object.
+ */
+public class RestMethods {
+
+ private final Map<String,List<RestMethodContext>> map;
+ private List<RestMethodContext> list;
+
+ /**
+ * Creates a new builder.
+ *
+ * @return A new builder.
+ */
+ public static RestMethodsBuilder create() {
+ return new RestMethodsBuilder();
+ }
+
+ RestMethods(RestMethodsBuilder builder) {
+ AMap<String,List<RestMethodContext>> m = AMap.create();
+ for (Map.Entry<String,TreeSet<RestMethodContext>> e :
builder.map.entrySet())
+ m.put(e.getKey(), AList.of(e.getValue()));
+ this.map = m;
+ this.list = AList.of(builder.set);
+ }
+
+ /**
+ * Finds the method that should handle the specified call.
+ *
+ * @param call The HTTP call.
+ * @return The method that should handle the specified call.
+ * @throws MethodNotAllowed If no methods implement the requested HTTP
method.
+ * @throws PreconditionFailed At least one method was found but it
didn't match one or more matchers.
+ * @throws NotFound HTTP method match was found but matching path was
not.
+ */
+ public RestMethodContext findMethod(RestCall call) throws
MethodNotAllowed, PreconditionFailed, NotFound {
+ String m = call.getMethod();
+
+ int rc = 0;
+ if (map.containsKey(m)) {
+ for (RestMethodContext mc : map.get(m)) {
+ int mrc = mc.match(call);
+ if (mrc == 2)
+ return mc;
+ rc = Math.max(rc, mrc);
+ }
+ }
+
+ if (map.containsKey("*")) {
+ for (RestMethodContext mc : map.get("*")) {
+ int mrc = mc.match(call);
+ if (mrc == 2)
+ return mc;
+ rc = Math.max(rc, mrc);
+ }
+ }
+
+ // 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 : list) {
+ if (! mc.getPathPattern().endsWith("/*")) {
+ int mrc = mc.match(call);
+ if (mrc == 2)
+ throw new MethodNotAllowed();
+ }
+ }
+ }
+
+ if (rc == 1)
+ throw new PreconditionFailed("Method ''{0}'' not found
on resource on path ''{1}'' with matching matcher.", m, call.getPathInfo());
+
+ throw new NotFound("Java method matching path ''{0}'' not found
on resource ''{1}''.", call.getPathInfo(),
call.getResource().getClass().getName());
+ }
+
+
+ /**
+ * Returns the list of method contexts in this object.
+ *
+ * @return An unmodifiable list of method contexts in this object.
+ */
+ public List<RestMethodContext> getMethodContexts() {
+ return Collections.unmodifiableList(list);
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java
new file mode 100644
index 0000000..1c7f97f
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodsBuilder.java
@@ -0,0 +1,61 @@
+//
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file *
+// * distributed with this work for additional information regarding copyright
ownership. The ASF licenses this file *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance *
+// * with the License. You may obtain a copy of the License at
*
+// *
*
+// * http://www.apache.org/licenses/LICENSE-2.0
*
+// *
*
+// * Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the *
+// * specific language governing permissions and limitations under the
License. *
+//
***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import java.util.*;
+
+import org.apache.juneau.collections.*;
+
+/**
+ * Builder for {@link RestMethods} object.
+ */
+public class RestMethodsBuilder {
+
+ TreeMap<String,TreeSet<RestMethodContext>> map = new TreeMap<>();
+ Set<RestMethodContext> set = ASet.of();
+
+ /**
+ * Adds a method context to this builder.
+ *
+ * @param mc The REST method context to add.
+ * @return Adds a method context to this builder.
+ */
+ public RestMethodsBuilder add(RestMethodContext mc) {
+ return add(mc.getHttpMethod(), mc);
+ }
+
+ /**
+ * Adds a method context to this builder.
+ *
+ * @param httpMethodName The HTTP method name.
+ * @param mc The REST method context to add.
+ * @return Adds a method context to this builder.
+ */
+ public RestMethodsBuilder 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;
+ }
+
+ /**
+ * Creates a new {@link RestMethods} object using the contents of this
builder.
+ *
+ * @return A new {@link RestMethods} object.
+ */
+ public RestMethods build() {
+ return new RestMethods(this);
+ }
+}
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestMethodContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestMethodContext.java
new file mode 100644
index 0000000..accaafe
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RrpcRestMethodContext.java
@@ -0,0 +1,92 @@
+//
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file *
+// * distributed with this work for additional information regarding copyright
ownership. The ASF licenses this file *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance *
+// * with the License. You may obtain a copy of the License at
*
+// *
*
+// * http://www.apache.org/licenses/LICENSE-2.0
*
+// *
*
+// * Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the *
+// * specific language governing permissions and limitations under the
License. *
+//
***************************************************************************************************************************
+package org.apache.juneau.rest;
+
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.rest.HttpRuntimeException.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import javax.servlet.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.exception.*;
+import org.apache.juneau.http.remote.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * A specialized {@link RestMethodContext} for handling <js>"RRPC"</js> HTTP
methods.
+ */
+public class RrpcRestMethodContext extends RestMethodContext {
+
+ private final RrpcInterfaceMeta meta;
+
+ /**
+ * Constructor.
+ *
+ * @param ps The property store containing the settings for this
context.
+ * @throws ServletException Problem with metadata was detected.
+ */
+ public RrpcRestMethodContext(PropertyStore ps) throws ServletException {
+ super(ps);
+
+ ClassMeta<?> interfaceClass =
getClassMeta(mi.inner().getGenericReturnType());
+ meta = new RrpcInterfaceMeta(interfaceClass.getInnerClass(),
null);
+ if (meta.getMethodsByPath().isEmpty())
+ throw new InternalServerError("Method {0} returns an
interface {1} that doesn't define any remote methods.", mi.getSignature(),
interfaceClass.getFullName());
+
+ }
+
+ @Override
+ public void invoke(RestCall call) throws Throwable {
+
+ super.invoke(call);
+
+ final Object o = call.getOutput();
+
+ if ("GET".equals(call.getMethod())) {
+ call.output(meta.getMethodsByPath().keySet());
+ return;
+
+ } else if ("POST".equals(call.getMethod())) {
+ String pip = call.getUrlPath().getPath();
+ if (pip.indexOf('/') != -1)
+ pip = pip.substring(pip.lastIndexOf('/')+1);
+ pip = urlDecode(pip);
+ RrpcInterfaceMethodMeta rmm =
meta.getMethodMetaByPath(pip);
+ if (rmm != null) {
+ Method m = rmm.getJavaMethod();
+ try {
+ RestRequest req = call.getRestRequest();
+ // Parse the args and invoke the method.
+ Parser p = req.getBody().getParser();
+ Object[] args = null;
+ if (m.getGenericParameterTypes().length
== 0)
+ args = new Object[0];
+ else {
+ try (Closeable in =
p.isReaderParser() ? req.getReader() : req.getInputStream()) {
+ args = p.parseArgs(in,
m.getGenericParameterTypes());
+ }
+ }
+ Object output = m.invoke(o, args);
+ call.output(output);
+ return;
+ } catch (Exception e) {
+ throw toHttpException(e,
InternalServerError.class);
+ }
+ }
+ }
+ throw new NotFound();
+ }
+}