Repository: quarkusio/quarkus
Issue: RESTEasy Reactive: Sub resource Path Params more than 2 levels back are not passed correctly

Describe the bug
Sub resource path params are not bound correctly in a scenario with more than two sub resource levels.
E.g. if path /trees/{treeId}/branches/{branchId}/leaves/{leafId} is handled by a sub resource "LeavesResource" which uses @PathParam("treeId") and @PathParam("branchId") and @PathParam("leafId") then treeId is not injected while the other two are. 

Expected behavior
Multiple levels should be supported. The correct behaviour is found in standard resteasy.

Actual behavior
The value is not injected causing difficult to track bugs.

To Reproduce
git clone https://github.com/bcluap/quarkus-examples.git
cd quarkus-examples/resteasy-reactive
mvn clean test

Configuration
Nothing abnormal

Screenshots
Run the reproducer to see failures. Change the pom to non-reactive mode and all tests pass

Environment (please complete the following information):
This is not environment-specific
999-SNAPSHOT and before

Additional context
NA

Changed files:
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/SubResourceRequestFilterTest.java
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java

diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/SubResourceRequestFilterTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/SubResourceRequestFilterTest.java
index a7f3f37a7b7..2daf58362e5 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/SubResourceRequestFilterTest.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/SubResourceRequestFilterTest.java
@@ -31,28 +31,42 @@ public class SubResourceRequestFilterTest {
                 @Override
                 public JavaArchive get() {
                     JavaArchive war = ShrinkWrap.create(JavaArchive.class);
-                    war.addClasses(RestResource.class, RestSubResource.class, SingleExecutionFilter.class);
+                    war.addClasses(RestResource.class, RestSubResource.class, SingleExecutionFilter.class,
+                            MiddleRestResource.class);
                     return war;
                 }
             });
 
     @Test
-    public void testAbortingRequestFilter() {
-        RestAssured.get("/sub-resource/geo")
+    public void testSubResourceFilter() {
+        RestAssured.get("/sub-resource/Bob/Builder")
                 .then()
                 .header("single-filter", Matchers.equalTo("once"))
-                .body(Matchers.equalTo("geo"))
+                .body(Matchers.equalTo("Bob Builder"))
                 .statusCode(200);
     }
 
     @Path("/")
     public static class RestResource {
 
+        @Inject
+        MiddleRestResource restSubResource;
+
+        @Path("sub-resource/{first}")
+        public MiddleRestResource hello(String first) {
+            return restSubResource;
+        }
+    }
+
+    @ApplicationScoped
+    @Path("/")
+    public static class MiddleRestResource {
+
         @Inject
         RestSubResource restSubResource;
 
-        @Path("sub-resource/{name}")
-        public RestSubResource hello(String name) {
+        @Path("{last}")
+        public RestSubResource hello() {
             return restSubResource;
         }
     }
@@ -61,8 +75,8 @@ public RestSubResource hello(String name) {
     public static class RestSubResource {
 
         @GET
-        public Response hello(HttpHeaders headers, @RestPath String name) {
-            return Response.ok(name).header("single-filter", headers.getHeaderString("single-filter")).build();
+        public Response hello(HttpHeaders headers, @RestPath String first, @RestPath String last) {
+            return Response.ok(first + " " + last).header("single-filter", headers.getHeaderString("single-filter")).build();
         }
     }
 
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
index 0b875e61aee..4b39cb27921 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java
@@ -58,11 +58,11 @@
      */
     private Object[] parameters;
     private RuntimeResource target;
+
     /**
-     * When a subresource has been located and the processing has been restarted (and thus target point to the new subresource),
-     * this field contains the target that resulted in the offloading to the new target
+     * info about path params and other data about previously matched sub resource locators
      */
-    private RuntimeResource locatorTarget;
+    private PreviousResource previousResource;
 
     /**
      * The parameter values extracted from the path.
@@ -79,11 +79,6 @@
      * Note: those are decoded.
      */
     private Object pathParamValues;
-    /**
-     * When a subresource has been located and the processing has been restarted (and thus target point to the new subresource),
-     * this field contains the pathParamValues of the target that resulted in the offloading to the new target
-     */
-    private Object locatorPathParamValues;
 
     private UriInfo uriInfo;
     /**
@@ -178,8 +173,7 @@ public void restart(RuntimeResource target, boolean setLocatorTarget) {
         position = 0;
         parameters = new Object[target.getParameterTypes().length];
         if (setLocatorTarget) {
-            this.locatorTarget = this.target;
-            this.locatorPathParamValues = this.pathParamValues;
+            previousResource = new PreviousResource(this.target, pathParamValues, previousResource);
         }
         this.target = target;
     }
@@ -222,10 +216,6 @@ public String getPathParam(int index) {
         return doGetPathParam(index, pathParamValues);
     }
 
-    public String getLocatorPathParam(int index) {
-        return doGetPathParam(index, locatorPathParamValues);
-    }
-
     private String doGetPathParam(int index, Object pathParamValues) {
         if (pathParamValues instanceof String[]) {
             return ((String[]) pathParamValues)[index];
@@ -320,10 +310,6 @@ public RuntimeResource getTarget() {
         return target;
     }
 
-    public RuntimeResource getLocatorTarget() {
-        return locatorTarget;
-    }
-
     public void mapExceptionIfPresent() {
         // this is called from the abort chain, but we can abort because we have a Response, or because
         // we got an exception
@@ -928,4 +914,76 @@ public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() {
     protected abstract Executor getEventLoop();
 
     public abstract Runnable registerTimer(long millis, Runnable task);
+
+    public String getResourceLocatorPathParam(String name) {
+        return getResourceLocatorPathParam(name, previousResource);
+    }
+
+    private String getResourceLocatorPathParam(String name, PreviousResource previousResource) {
+        if (previousResource == null) {
+            return null;
+        }
+
+        int index = 0;
+        URITemplate classPath = previousResource.locatorTarget.getClassPath();
+        if (classPath != null) {
+            for (URITemplate.TemplateComponent component : classPath.components) {
+                if (component.name != null) {
+                    if (component.name.equals(name)) {
+                        return doGetPathParam(index, previousResource.locatorPathParamValues);
+                    }
+                    index++;
+                } else if (component.names != null) {
+                    for (String nm : component.names) {
+                        if (nm.equals(name)) {
+                            return doGetPathParam(index, previousResource.locatorPathParamValues);
+                        }
+                    }
+                    index++;
+                }
+            }
+        }
+        for (URITemplate.TemplateComponent component : previousResource.locatorTarget.getPath().components) {
+            if (component.name != null) {
+                if (component.name.equals(name)) {
+                    return doGetPathParam(index, previousResource.locatorPathParamValues);
+                }
+                index++;
+            } else if (component.names != null) {
+                for (String nm : component.names) {
+                    if (nm.equals(name)) {
+                        return doGetPathParam(index, previousResource.locatorPathParamValues);
+                    }
+                }
+                index++;
+            }
+        }
+        return getResourceLocatorPathParam(name, previousResource.prev);
+    }
+
+    static class PreviousResource {
+
+        public PreviousResource(RuntimeResource locatorTarget, Object locatorPathParamValues, PreviousResource prev) {
+            this.locatorTarget = locatorTarget;
+            this.locatorPathParamValues = locatorPathParamValues;
+            this.prev = prev;
+        }
+
+        /**
+         * When a subresource has been located and the processing has been restarted (and thus target point to the new
+         * subresource),
+         * this field contains the target that resulted in the offloading to the new target
+         */
+        private final RuntimeResource locatorTarget;
+
+        /**
+         * When a subresource has been located and the processing has been restarted (and thus target point to the new
+         * subresource),
+         * this field contains the pathParamValues of the target that resulted in the offloading to the new target
+         */
+        private final Object locatorPathParamValues;
+
+        private final PreviousResource prev;
+
+    }
 }
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java
index 76bb46097e2..835be0b5153 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/LocatableResourcePathParamExtractor.java
@@ -1,7 +1,6 @@
 package org.jboss.resteasy.reactive.server.core.parameters;
 
 import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
-import org.jboss.resteasy.reactive.server.mapping.URITemplate;
 
 public class LocatableResourcePathParamExtractor implements ParameterExtractor {
 
@@ -13,48 +12,7 @@ public LocatableResourcePathParamExtractor(String name) {
 
     @Override
     public Object extractParameter(ResteasyReactiveRequestContext context) {
-        int index = findPathParamIndex(context.getLocatorTarget().getClassPath(), context.getLocatorTarget().getPath());
-        if (index >= 0) {
-            return context.getLocatorPathParam(index);
-        }
-        return null;
-    }
-
-    private int findPathParamIndex(URITemplate classPathTemplate, URITemplate methodPathTemplate) {
-        int index = 0;
-        if (classPathTemplate != null) {
-            for (URITemplate.TemplateComponent component : classPathTemplate.components) {
-                if (component.name != null) {
-                    if (component.name.equals(this.name)) {
-                        return index;
-                    }
-                    index++;
-                } else if (component.names != null) {
-                    for (String nm : component.names) {
-                        if (nm.equals(this.name)) {
-                            return index;
-                        }
-                    }
-                    index++;
-                }
-            }
-        }
-        for (URITemplate.TemplateComponent component : methodPathTemplate.components) {
-            if (component.name != null) {
-                if (component.name.equals(this.name)) {
-                    return index;
-                }
-                index++;
-            } else if (component.names != null) {
-                for (String nm : component.names) {
-                    if (nm.equals(this.name)) {
-                        return index;
-                    }
-                }
-                index++;
-            }
-        }
-        return -1;
+        return context.getResourceLocatorPathParam(name);
     }
 
 }