You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/10/19 15:25:40 UTC

[camel] branch main updated: CAMEL-8306: Add support for wildcards to match on prefix (#11638)

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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 0c3ba620fa0 CAMEL-8306: Add support for wildcards to match on prefix (#11638)
0c3ba620fa0 is described below

commit 0c3ba620fa040d8afc270e52a6094370d389a939
Author: TUCJVXCB <tt...@hotmail.com>
AuthorDate: Thu Oct 19 23:25:33 2023 +0800

    CAMEL-8306: Add support for wildcards to match on prefix (#11638)
    
    * CAMEL-8306: Add support for wildcards to match on prefix
    
    * CAMEL-8306: replace * import
    
    * CAMEL-8306: add unit test
    
    * CAMEL-8306: update docs
    
    * CAMEL-8306: adjust test unit
    
    * CAMEL-8306: adjust priority
    
    * CAMEL-8306: pre-compiled consumer path
    
    * CAMEL-8306: update unit test
    
    * CAMEL-8306: add unit test
---
 .../org/apache/camel/catalog/components/rest.json  |   2 +-
 .../org/apache/camel/http/common/CamelServlet.java |   3 +
 .../apache/camel/http/common/CamelServletTest.java |  11 +-
 .../HttpServerMultiplexChannelHandler.java         |   2 +
 .../org/apache/camel/component/rest/rest.json      |   2 +-
 .../apache/camel/component/rest/RestEndpoint.java  |   2 +-
 .../undertow/handlers/RestRootHandler.java         |   2 +
 .../support/RestConsumerContextPathMatcher.java    | 117 ++++++++++++++++-----
 .../RestConsumerContextPathMatcherTest.java        |  46 ++++++++
 9 files changed, 154 insertions(+), 33 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest.json
index a1a9b9e973c..e11adcdfaa6 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest.json
@@ -42,7 +42,7 @@
   },
   "properties": {
     "method": { "index": 0, "kind": "path", "displayName": "Method", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "enum": [ "get", "post", "put", "delete", "patch", "head", "trace", "connect", "options" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "HTTP method to use." },
-    "path": { "index": 1, "kind": "path", "displayName": "Path", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The base path" },
+    "path": { "index": 1, "kind": "path", "displayName": "Path", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The base path, can use &#42; as path suffix to support wildcard HTTP route matching." },
     "uriTemplate": { "index": 2, "kind": "path", "displayName": "Uri Template", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The uri template" },
     "consumes": { "index": 3, "kind": "parameter", "displayName": "Consumes", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Media type such as: 'text\/xml', or 'application\/json' this REST service accepts. By default we accept all kinds of types." },
     "inType": { "index": 4, "kind": "parameter", "displayName": "In Type", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "To declare the incoming POJO binding type as a FQN class name" },
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java
index d25aa8ae152..25e68e105c8 100644
--- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/CamelServlet.java
@@ -43,6 +43,7 @@ import org.apache.camel.Processor;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.spi.ExecutorServiceManager;
 import org.apache.camel.support.LifecycleStrategySupport;
+import org.apache.camel.support.RestConsumerContextPathMatcher;
 import org.apache.camel.util.ObjectHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -420,12 +421,14 @@ public class CamelServlet extends HttpServlet implements HttpRegistryProvider {
             throw new IllegalStateException("Duplicate request path for " + endpointUri);
         }
         consumers.put(endpointUri, consumer);
+        RestConsumerContextPathMatcher.register(consumer.getPath());
     }
 
     @Override
     public void disconnect(HttpConsumer consumer) {
         log.debug("Disconnecting consumer: {}", consumer);
         consumers.remove(consumer.getEndpoint().getEndpointUri());
+        RestConsumerContextPathMatcher.unRegister(consumer.getPath());
     }
 
     @Override
diff --git a/components/camel-http-common/src/test/java/org/apache/camel/http/common/CamelServletTest.java b/components/camel-http-common/src/test/java/org/apache/camel/http/common/CamelServletTest.java
index 1384628122e..7d40887f28a 100644
--- a/components/camel-http-common/src/test/java/org/apache/camel/http/common/CamelServletTest.java
+++ b/components/camel-http-common/src/test/java/org/apache/camel/http/common/CamelServletTest.java
@@ -16,10 +16,16 @@
  */
 package org.apache.camel.http.common;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.LinkedHashMap;
+
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
 import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.util.UnsafeUriCharactersEncoder;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -28,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 public class CamelServletTest {
 
     @Test
-    public void testDuplicatedServletPath() {
+    public void testDuplicatedServletPath() throws URISyntaxException {
         CamelServlet camelServlet = new CamelServlet();
 
         HttpCommonEndpoint httpCommonEndpoint = new HttpCommonEndpoint() {
@@ -47,6 +53,9 @@ public class CamelServletTest {
         DefaultCamelContext dc = new DefaultCamelContext();
 
         httpCommonEndpoint.setEndpointUriIfNotSpecified("rest:post://camel.apache.org");
+        httpCommonEndpoint.setHttpUri(URISupport.createRemainingURI(
+                new URI(UnsafeUriCharactersEncoder.encodeHttpURI("servlet:/camel.apache.org?httpMethodRestrict=GET")),
+                new LinkedHashMap<>()));
         httpCommonEndpoint.setCamelContext(dc);
 
         HttpConsumer httpConsumer1 = new HttpConsumer(httpCommonEndpoint, null);
diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
index 151c32beae7..7f69da8fc92 100644
--- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
+++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
@@ -87,6 +87,7 @@ public class HttpServerMultiplexChannelHandler extends SimpleChannelInboundHandl
     @Override
     public void addConsumer(NettyHttpConsumer consumer) {
         consumers.add(new HttpServerChannelHandler(consumer));
+        RestConsumerContextPathMatcher.register(consumer.getConfiguration().getPath());
     }
 
     @Override
@@ -94,6 +95,7 @@ public class HttpServerMultiplexChannelHandler extends SimpleChannelInboundHandl
         for (HttpServerChannelHandler handler : consumers) {
             if (handler.getConsumer() == consumer) {
                 consumers.remove(handler);
+                RestConsumerContextPathMatcher.unRegister(consumer.getConfiguration().getPath());
             }
         }
     }
diff --git a/components/camel-rest/src/generated/resources/org/apache/camel/component/rest/rest.json b/components/camel-rest/src/generated/resources/org/apache/camel/component/rest/rest.json
index a1a9b9e973c..e11adcdfaa6 100644
--- a/components/camel-rest/src/generated/resources/org/apache/camel/component/rest/rest.json
+++ b/components/camel-rest/src/generated/resources/org/apache/camel/component/rest/rest.json
@@ -42,7 +42,7 @@
   },
   "properties": {
     "method": { "index": 0, "kind": "path", "displayName": "Method", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "enum": [ "get", "post", "put", "delete", "patch", "head", "trace", "connect", "options" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "HTTP method to use." },
-    "path": { "index": 1, "kind": "path", "displayName": "Path", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The base path" },
+    "path": { "index": 1, "kind": "path", "displayName": "Path", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The base path, can use &#42; as path suffix to support wildcard HTTP route matching." },
     "uriTemplate": { "index": 2, "kind": "path", "displayName": "Uri Template", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The uri template" },
     "consumes": { "index": 3, "kind": "parameter", "displayName": "Consumes", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Media type such as: 'text\/xml', or 'application\/json' this REST service accepts. By default we accept all kinds of types." },
     "inType": { "index": 4, "kind": "parameter", "displayName": "In Type", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "To declare the incoming POJO binding type as a FQN class name" },
diff --git a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
index 8d0dc8c01e9..064232a1f7c 100644
--- a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
+++ b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
@@ -131,7 +131,7 @@ public class RestEndpoint extends DefaultEndpoint {
     }
 
     /**
-     * The base path
+     * The base path, can use &#42; as path suffix to support wildcard HTTP route matching.
      */
     public void setPath(String path) {
         this.path = path;
diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/RestRootHandler.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/RestRootHandler.java
index 93ae1c6e88e..ca9d12ffc87 100644
--- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/RestRootHandler.java
+++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/RestRootHandler.java
@@ -62,6 +62,7 @@ public class RestRootHandler implements HttpHandler {
      */
     public void addConsumer(UndertowConsumer consumer) {
         consumers.add(consumer);
+        RestConsumerContextPathMatcher.register(consumer.getEndpoint().getHttpURI().getPath());
     }
 
     /**
@@ -69,6 +70,7 @@ public class RestRootHandler implements HttpHandler {
      */
     public void removeConsumer(UndertowConsumer consumer) {
         consumers.remove(consumer);
+        RestConsumerContextPathMatcher.unRegister(consumer.getEndpoint().getHttpURI().getPath());
     }
 
     /**
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java b/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java
index bda24e6bc0d..d8183fad16c 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java
@@ -24,18 +24,23 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.OptionalInt;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
  * A context path matcher when using rest-dsl that allows components to reuse the same matching logic.
  * <p/>
- * The component should use the {@link #matchBestPath(String, String, java.util.List)} with the request details and the
- * matcher returns the best matched, or <tt>null</tt> if none could be determined.
+ * The component should use the {@link #matchBestPath(String, String, List)} with the request details and the matcher
+ * returns the best matched, or <tt>null</tt> if none could be determined.
  * <p/>
  * The {@link ConsumerPath} is used for the components to provide the details to the matcher.
  */
 public final class RestConsumerContextPathMatcher {
 
+    private static final Map<String, Pattern> PATH_PATTERN = new ConcurrentHashMap<>();
+
     private RestConsumerContextPathMatcher() {
     }
 
@@ -156,9 +161,9 @@ public final class RestConsumerContextPathMatcher {
             }
         }
 
-        // if there are no wildcards, then select the matching with the longest path
-        boolean noWildcards = candidates.stream().allMatch(p -> countWildcards(p.getConsumerPath()) == 0);
-        if (noWildcards) {
+        // if there are no uri template, then select the matching with the longest path
+        boolean noCurlyBraces = candidates.stream().allMatch(p -> countCurlyBraces(p.getConsumerPath()) == 0);
+        if (noCurlyBraces) {
             // grab first which is the longest that matched the request path
             answer = candidates.stream()
                     .filter(c -> matchPath(requestPath, c.getConsumerPath(), c.isMatchOnUriPrefix()))
@@ -170,27 +175,28 @@ public final class RestConsumerContextPathMatcher {
             return answer;
         }
 
-        // then match by wildcard path
+        // then match by uri template path
         it = candidates.iterator();
+        List<ConsumerPath<T>> uriTemplateCandidates = new ArrayList<>();
         while (it.hasNext()) {
-            ConsumerPath<?> consumer = it.next();
+            ConsumerPath<T> consumer = it.next();
             // filter non matching paths
-            if (!matchRestPath(requestPath, consumer.getConsumerPath(), true)) {
-                it.remove();
+            if (matchRestPath(requestPath, consumer.getConsumerPath(), true)) {
+                uriTemplateCandidates.add(consumer);
             }
         }
 
-        // if there is multiple candidates with wildcards then pick anyone with the least number of wildcards
+        // if there is multiple candidates with uri template then pick anyone with the least number of uri template
         ConsumerPath<T> best = null;
         Map<Integer, List<ConsumerPath<T>>> pathMap = new HashMap<>();
-        if (candidates.size() > 1) {
-            it = candidates.iterator();
+        if (uriTemplateCandidates.size() > 1) {
+            it = uriTemplateCandidates.iterator();
             while (it.hasNext()) {
                 ConsumerPath<T> entry = it.next();
-                int wildcards = countWildcards(entry.getConsumerPath());
-                if (wildcards > 0) {
-                    List<ConsumerPath<T>> consumerPathsLst = pathMap.computeIfAbsent(wildcards, key -> new ArrayList<>());
-                    consumerPathsLst.add(entry);
+                int curlyBraces = countCurlyBraces(entry.getConsumerPath());
+                if (curlyBraces > 0) {
+                    List<ConsumerPath<T>> consumerPathsList = pathMap.computeIfAbsent(curlyBraces, key -> new ArrayList<>());
+                    consumerPathsList.add(entry);
                 }
             }
 
@@ -206,19 +212,57 @@ public final class RestConsumerContextPathMatcher {
             }
 
             if (best != null) {
-                // pick the best among the wildcards
+                // pick the best among uri template
                 answer = best;
             }
         }
 
-        // if there is one left then its our answer
-        if (answer == null && candidates.size() == 1) {
-            answer = candidates.get(0);
+        // if there is one left then it's our answer
+        if (answer == null && uriTemplateCandidates.size() == 1) {
+            return uriTemplateCandidates.get(0);
+        }
+
+        // last match by wildcard path
+        it = candidates.iterator();
+        while (it.hasNext()) {
+            ConsumerPath<T> consumer = it.next();
+            // filter non matching paths
+            if (matchWildCard(requestPath, consumer.getConsumerPath())) {
+                answer = consumer;
+                break;
+            }
         }
 
         return answer;
     }
 
+    /**
+     * Pre-compiled consumer path for wildcard match
+     * @param consumerPath a consumer path
+     */
+    public static void register(String consumerPath) {
+        // Convert URI template to a regex pattern
+        String regex = consumerPath
+                .replace("/", "\\/")
+                .replace("{", "(?<")
+                .replace("}", ">[^\\/]+)");
+
+        // Add support for wildcard * as path suffix
+        regex = regex.replace("*", ".*");
+
+        // Match the provided path against the regex pattern
+        Pattern pattern = Pattern.compile(regex);
+        PATH_PATTERN.put(consumerPath, pattern);
+    }
+
+    /**
+     * if the rest consumer is removed, we also remove pattern cache.
+     * @param consumerPath a consumer path
+     */
+    public static void unRegister(String consumerPath) {
+        PATH_PATTERN.remove(consumerPath);
+    }
+
     /**
      *
      * @param  requestMethod The request method
@@ -257,10 +301,10 @@ public final class RestConsumerContextPathMatcher {
      * Matches the given request path with the configured consumer path
      *
      * @param  requestPath  the request path
-     * @param  consumerPath the consumer path which may use { } tokens
+     * @param  isUriTemplate the consumer path which may use { } tokens
      * @return              <tt>true</tt> if matched, <tt>false</tt> otherwise
      */
-    private static boolean matchRestPath(String requestPath, String consumerPath, boolean wildcard) {
+    private static boolean matchRestPath(String requestPath, String consumerPath, boolean isUriTemplate) {
         // deal with null parameters
         if (requestPath == null && consumerPath == null) {
             return true;
@@ -297,7 +341,7 @@ public final class RestConsumerContextPathMatcher {
             String p1 = requestPaths[i];
             String p2 = consumerPaths[i];
 
-            if (wildcard && p2.startsWith("{") && p2.endsWith("}")) {
+            if (isUriTemplate && p2.startsWith("{") && p2.endsWith("}")) {
                 // always matches
                 continue;
             }
@@ -312,13 +356,13 @@ public final class RestConsumerContextPathMatcher {
     }
 
     /**
-     * Counts the number of wildcards in the path
+     * Counts the number of uri template's curlyBraces in the path
      *
      * @param  consumerPath the consumer path which may use { } tokens
-     * @return              number of wildcards, or <tt>0</tt> if no wildcards
+     * @return              number of curlyBraces, or <tt>0</tt> if no curlyBraces
      */
-    private static int countWildcards(String consumerPath) {
-        int wildcards = 0;
+    private static int countCurlyBraces(String consumerPath) {
+        int curlyBraces = 0;
 
         // remove starting/ending slashes
         if (consumerPath.startsWith("/")) {
@@ -331,11 +375,26 @@ public final class RestConsumerContextPathMatcher {
         String[] consumerPaths = consumerPath.split("/");
         for (String p2 : consumerPaths) {
             if (p2.startsWith("{") && p2.endsWith("}")) {
-                wildcards++;
+                curlyBraces++;
             }
         }
 
-        return wildcards;
+        return curlyBraces;
+    }
+
+    private static boolean matchWildCard(String requestPath, String consumerPath) {
+        if (!requestPath.endsWith("/")) {
+            requestPath = requestPath + "/";
+        }
+
+        Pattern pattern = PATH_PATTERN.get(consumerPath);
+        if (pattern == null) {
+            return false;
+        }
+
+        Matcher matcher = pattern.matcher(requestPath);
+
+        return matcher.matches();
     }
 
 }
diff --git a/core/camel-support/src/test/java/org/apache/camel/support/RestConsumerContextPathMatcherTest.java b/core/camel-support/src/test/java/org/apache/camel/support/RestConsumerContextPathMatcherTest.java
index a9b3fac1b72..d079e03b701 100644
--- a/core/camel-support/src/test/java/org/apache/camel/support/RestConsumerContextPathMatcherTest.java
+++ b/core/camel-support/src/test/java/org/apache/camel/support/RestConsumerContextPathMatcherTest.java
@@ -79,4 +79,50 @@ public class RestConsumerContextPathMatcherTest {
                 "/camel/a/b/3", consumerPaths);
         assertEquals(path.getConsumerPath(), "/camel/a/b/{c}");
     }
+
+    @Test
+    public void testRestConsumerContextPathMatcherWithWildcard() {
+        List<RestConsumerContextPathMatcher.ConsumerPath<MockConsumerPath>> consumerPaths = new ArrayList<>();
+        consumerPaths.add(new MockConsumerPath("GET", "/camel/myapp/info"));
+        consumerPaths.add(new MockConsumerPath("GET", "/camel/myapp/{id}"));
+        consumerPaths.add(new MockConsumerPath("GET", "/camel/myapp/order/*"));
+
+        RestConsumerContextPathMatcher.register("/camel/myapp/order/*");
+
+        RestConsumerContextPathMatcher.ConsumerPath<?> path1 = RestConsumerContextPathMatcher.matchBestPath("GET",
+                "/camel/myapp/info", consumerPaths);
+
+        RestConsumerContextPathMatcher.ConsumerPath<?> path2 = RestConsumerContextPathMatcher.matchBestPath("GET",
+                "/camel/myapp/1", consumerPaths);
+
+        RestConsumerContextPathMatcher.ConsumerPath<?> path3 = RestConsumerContextPathMatcher.matchBestPath("GET",
+                "/camel/myapp/order/foo", consumerPaths);
+
+        assertEquals(path1.getConsumerPath(), "/camel/myapp/info");
+        assertEquals(path2.getConsumerPath(), "/camel/myapp/{id}");
+        assertEquals(path3.getConsumerPath(), "/camel/myapp/order/*");
+    }
+
+    @Test
+    public void testRestConsumerContextPathMatcherOrder() {
+        List<RestConsumerContextPathMatcher.ConsumerPath<MockConsumerPath>> consumerPaths = new ArrayList<>();
+        consumerPaths.add(new MockConsumerPath("GET", "/camel/*"));
+        consumerPaths.add(new MockConsumerPath("GET", "/camel/foo"));
+        consumerPaths.add(new MockConsumerPath("GET", "/camel/foo/{id}"));
+
+        RestConsumerContextPathMatcher.register("/camel/*");
+
+        RestConsumerContextPathMatcher.ConsumerPath<?> path1 = RestConsumerContextPathMatcher.matchBestPath("GET",
+                "/camel/foo", consumerPaths);
+
+        RestConsumerContextPathMatcher.ConsumerPath<?> path2 = RestConsumerContextPathMatcher.matchBestPath("GET",
+                "/camel/foo/bar", consumerPaths);
+
+        RestConsumerContextPathMatcher.ConsumerPath<?> path3 = RestConsumerContextPathMatcher.matchBestPath("GET",
+                "/camel/foo/bar/1", consumerPaths);
+
+        assertEquals(path1.getConsumerPath(), "/camel/foo");
+        assertEquals(path2.getConsumerPath(), "/camel/foo/{id}");
+        assertEquals(path3.getConsumerPath(), "/camel/*");
+    }
 }