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();
+       }
+}

Reply via email to