Repository: camel Updated Branches: refs/heads/master 33912cf79 -> 555627d49
CAMEL-10691: HttpRestServletResolveConsumerStrategy should pick the path with longest prefix match Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/a90d28d1 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/a90d28d1 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/a90d28d1 Branch: refs/heads/master Commit: a90d28d1c48594bf3f1765a5dbe34c07f99a9960 Parents: 33912cf Author: Claus Ibsen <davscl...@apache.org> Authored: Tue Jan 17 13:26:05 2017 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Tue Jan 17 13:26:54 2017 +0100 ---------------------------------------------------------------------- .../support/RestConsumerContextPathMatcher.java | 43 +++++++- .../camel/http/common/HttpRestConsumerPath.java | 53 +++++++++ .../HttpRestServletResolveConsumerStrategy.java | 19 +--- .../jetty/JettyLongestContextPathMatchTest.java | 107 +++++++++++++++++++ 4 files changed, 203 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/a90d28d1/camel-core/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java b/camel-core/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java index 2ae50a7..f3c22af 100644 --- a/camel-core/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java +++ b/camel-core/src/main/java/org/apache/camel/support/RestConsumerContextPathMatcher.java @@ -17,9 +17,11 @@ package org.apache.camel.support; import java.util.ArrayList; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; /** * A context path matcher when using rest-dsl that allows components to reuse the same matching logic. @@ -30,11 +32,10 @@ import java.util.Locale; * The {@link ConsumerPath} is used for the components to provide the details to the matcher. */ public final class RestConsumerContextPathMatcher { + private RestConsumerContextPathMatcher() { - } - /** * Consumer path details which must be implemented and provided by the components. */ @@ -55,6 +56,11 @@ public final class RestConsumerContextPathMatcher { */ T getConsumer(); + /** + * Whether the consumer match on uri prefix + */ + boolean isMatchOnUriPrefix(); + } /** @@ -74,6 +80,21 @@ public final class RestConsumerContextPathMatcher { return false; } + // remove starting/ending slashes + if (requestPath.startsWith("/")) { + requestPath = requestPath.substring(1); + } + if (requestPath.endsWith("/")) { + requestPath = requestPath.substring(0, requestPath.length() - 1); + } + // remove starting/ending slashes + if (consumerPath.startsWith("/")) { + consumerPath = consumerPath.substring(1); + } + if (consumerPath.endsWith("/")) { + consumerPath = consumerPath.substring(0, consumerPath.length() - 1); + } + String p1 = requestPath.toLowerCase(Locale.ENGLISH); String p2 = consumerPath.toLowerCase(Locale.ENGLISH); @@ -118,6 +139,16 @@ 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) { + // grab first which is the longest that matched the request path + answer = candidates.stream() + .filter(c -> matchPath(requestPath, c.getConsumerPath(), c.isMatchOnUriPrefix())) + // sort by longest by inverting the sort by multiply with -1 + .sorted(Comparator.comparingInt(o -> -1 * o.getConsumerPath().length())).findFirst().orElse(null); + } + // then match by wildcard path if (answer == null) { it = candidates.iterator(); @@ -183,6 +214,14 @@ public final class RestConsumerContextPathMatcher { * @return <tt>true</tt> if matched, <tt>false</tt> otherwise */ private static boolean matchRestPath(String requestPath, String consumerPath, boolean wildcard) { + // deal with null parameters + if (requestPath == null && consumerPath == null) { + return true; + } + if (requestPath == null || consumerPath == null) { + return false; + } + // remove starting/ending slashes if (requestPath.startsWith("/")) { requestPath = requestPath.substring(1); http://git-wip-us.apache.org/repos/asf/camel/blob/a90d28d1/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java new file mode 100644 index 0000000..aed2270 --- /dev/null +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestConsumerPath.java @@ -0,0 +1,53 @@ +/** + * 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.camel.http.common; + +import org.apache.camel.support.RestConsumerContextPathMatcher; + +public class HttpRestConsumerPath implements RestConsumerContextPathMatcher.ConsumerPath<HttpConsumer> { + + private final HttpConsumer consumer; + + public HttpRestConsumerPath(HttpConsumer consumer) { + this.consumer = consumer; + } + + @Override + public String getRestrictMethod() { + return consumer.getEndpoint().getHttpMethodRestrict(); + } + + @Override + public String getConsumerPath() { + return consumer.getPath(); + } + + @Override + public HttpConsumer getConsumer() { + return consumer; + } + + @Override + public boolean isMatchOnUriPrefix() { + return consumer.getEndpoint().isMatchOnUriPrefix(); + } + + @Override + public String toString() { + return getConsumerPath(); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a90d28d1/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java index 7fa07cd..ae412b3 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpRestServletResolveConsumerStrategy.java @@ -42,24 +42,9 @@ public class HttpRestServletResolveConsumerStrategy extends HttpServletResolveCo return null; } - List<RestConsumerContextPathMatcher.ConsumerPath> paths = new ArrayList<RestConsumerContextPathMatcher.ConsumerPath>(); + List<RestConsumerContextPathMatcher.ConsumerPath> paths = new ArrayList<>(); for (final Map.Entry<String, HttpConsumer> entry : consumers.entrySet()) { - paths.add(new RestConsumerContextPathMatcher.ConsumerPath<HttpConsumer>() { - @Override - public String getRestrictMethod() { - return entry.getValue().getEndpoint().getHttpMethodRestrict(); - } - - @Override - public String getConsumerPath() { - return entry.getValue().getPath(); - } - - @Override - public HttpConsumer getConsumer() { - return entry.getValue(); - } - }); + paths.add(new HttpRestConsumerPath(entry.getValue())); } RestConsumerContextPathMatcher.ConsumerPath<HttpConsumer> best = RestConsumerContextPathMatcher.matchBestPath(method, path, paths); http://git-wip-us.apache.org/repos/asf/camel/blob/a90d28d1/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/JettyLongestContextPathMatchTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/JettyLongestContextPathMatchTest.java b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/JettyLongestContextPathMatchTest.java new file mode 100644 index 0000000..f5f2e2f --- /dev/null +++ b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/JettyLongestContextPathMatchTest.java @@ -0,0 +1,107 @@ +/** + * 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.camel.component.jetty; + +import org.apache.camel.builder.RouteBuilder; +import org.junit.Test; + +/** + * Test that longest context-path is preferred + */ +public class JettyLongestContextPathMatchTest extends BaseJettyTest { + + @Test + public void testLongest() throws Exception { + getMockEndpoint("mock:aaa").expectedMessageCount(1); + getMockEndpoint("mock:bbb").expectedMessageCount(0); + getMockEndpoint("mock:ccc").expectedMessageCount(0); + getMockEndpoint("mock:ddd").expectedMessageCount(0); + template.sendBody("http://localhost:{{port}}/myapp/aaa", null); + assertMockEndpointsSatisfied(); + + resetMocks(); + + getMockEndpoint("mock:aaa").expectedMessageCount(1); + getMockEndpoint("mock:bbb").expectedMessageCount(0); + getMockEndpoint("mock:ccc").expectedMessageCount(0); + getMockEndpoint("mock:ddd").expectedMessageCount(0); + template.sendBody("http://localhost:{{port}}/myapp/aaa/ccc", null); + assertMockEndpointsSatisfied(); + + resetMocks(); + + getMockEndpoint("mock:aaa").expectedMessageCount(0); + getMockEndpoint("mock:bbb").expectedMessageCount(1); + getMockEndpoint("mock:ccc").expectedMessageCount(0); + getMockEndpoint("mock:ddd").expectedMessageCount(0); + template.sendBody("http://localhost:{{port}}/myapp/aaa/bbb", null); + assertMockEndpointsSatisfied(); + + resetMocks(); + + getMockEndpoint("mock:aaa").expectedMessageCount(0); + getMockEndpoint("mock:bbb").expectedMessageCount(1); + getMockEndpoint("mock:ccc").expectedMessageCount(0); + getMockEndpoint("mock:ddd").expectedMessageCount(0); + template.sendBody("http://localhost:{{port}}/myapp/aaa/bbb/foo", null); + assertMockEndpointsSatisfied(); + + resetMocks(); + + getMockEndpoint("mock:aaa").expectedMessageCount(0); + getMockEndpoint("mock:bbb").expectedMessageCount(0); + getMockEndpoint("mock:ccc").expectedMessageCount(1); + getMockEndpoint("mock:ddd").expectedMessageCount(0); + template.sendBody("http://localhost:{{port}}/myapp/aaa/bbb/ccc/", null); + assertMockEndpointsSatisfied(); + + resetMocks(); + + getMockEndpoint("mock:aaa").expectedMessageCount(0); + getMockEndpoint("mock:bbb").expectedMessageCount(0); + getMockEndpoint("mock:ccc").expectedMessageCount(1); + getMockEndpoint("mock:ddd").expectedMessageCount(0); + template.sendBody("http://localhost:{{port}}/myapp/aaa/bbb/ccc/foo", null); + assertMockEndpointsSatisfied(); + + resetMocks(); + + getMockEndpoint("mock:aaa").expectedMessageCount(0); + getMockEndpoint("mock:bbb").expectedMessageCount(0); + getMockEndpoint("mock:ccc").expectedMessageCount(0); + getMockEndpoint("mock:ddd").expectedMessageCount(1); + template.sendBody("http://localhost:{{port}}/myapp/aaa/ddd/eee/fff/foo", null); + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("jetty:http://localhost:{{port}}/myapp/aaa/?matchOnUriPrefix=true").to("mock:aaa"); + + from("jetty:http://localhost:{{port}}/myapp/aaa/bbb/ccc/?matchOnUriPrefix=true").to("mock:ccc"); + + from("jetty:http://localhost:{{port}}/myapp/aaa/ddd/eee/fff/?matchOnUriPrefix=true").to("mock:ddd"); + + from("jetty:http://localhost:{{port}}/myapp/aaa/bbb/?matchOnUriPrefix=true").to("mock:bbb"); + + } + }; + } + +}