CAMEL-7620: Rest DSL. Enlist rest services in RestRegistry and JMX.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/7ff40206 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/7ff40206 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/7ff40206 Branch: refs/heads/master Commit: 7ff40206553e5d920ae72fc1c087f89880071125 Parents: e239c0f Author: Claus Ibsen <davscl...@apache.org> Authored: Wed Aug 6 14:02:57 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Wed Aug 6 14:42:23 2014 +0200 ---------------------------------------------------------------------- ...JettyRestServletResolveConsumerStrategy.java | 79 +++++++++++++++--- .../jetty/rest/RestPathMatchingTest.java | 84 +++++++++++++------- ...rvletRestServletResolveConsumerStrategy.java | 81 ++++++++++++++++--- .../camel/example/rest/UserRouteBuilder.java | 10 +-- .../src/main/resources/camel-config-xml.xml | 14 ++-- .../src/main/webapp/index.html | 18 ++--- 6 files changed, 215 insertions(+), 71 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/7ff40206/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestServletResolveConsumerStrategy.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestServletResolveConsumerStrategy.java b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestServletResolveConsumerStrategy.java index 34f8ce2..1a7e05a 100644 --- a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestServletResolveConsumerStrategy.java +++ b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestServletResolveConsumerStrategy.java @@ -16,12 +16,15 @@ */ package org.apache.camel.component.jetty; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.camel.component.http.HttpConsumer; import org.apache.camel.component.http.HttpServletResolveConsumerStrategy; -import org.apache.camel.util.ObjectHelper; /** * A {@link org.apache.camel.component.http.HttpServletResolveConsumerStrategy} that supports the Rest DSL. @@ -30,24 +33,61 @@ public class JettyRestServletResolveConsumerStrategy extends HttpServletResolveC @Override public HttpConsumer resolve(HttpServletRequest request, Map<String, HttpConsumer> consumers) { + HttpConsumer answer = null; + String path = request.getPathInfo(); if (path == null) { return null; } + String method = request.getMethod(); + if (method == null) { + return null; + } - for (String key : consumers.keySet()) { - if (useRestMatching(key) && matchRestPath(path, key)) { - return consumers.get(key); + List<HttpConsumer> candidates = new ArrayList<HttpConsumer>(); + + // first match by http method + for (Map.Entry<String, HttpConsumer> entry : consumers.entrySet()) { + String restrict = entry.getValue().getEndpoint().getHttpMethodRestrict(); + if (matchRestMethod(method, restrict)) { + candidates.add(entry.getValue()); } } - // fallback to default - return super.resolve(request, consumers); - } + // then see if we got a direct match + Iterator<HttpConsumer> it = candidates.iterator(); + while (it.hasNext()) { + HttpConsumer consumer = it.next(); + String consumerPath = consumer.getPath(); + if (matchRestPath(path, consumerPath, false)) { + answer = consumer; + break; + } + } + + // then match by non wildcard path + if (answer == null) { + it = candidates.iterator(); + while (it.hasNext()) { + HttpConsumer consumer = it.next(); + String consumerPath = consumer.getPath(); + if (!matchRestPath(path, consumerPath, true)) { + it.remove(); + } + } + + // there should only be one + if (candidates.size() == 1) { + answer = candidates.get(0); + } + } + + if (answer == null) { + // fallback to default + answer = super.resolve(request, consumers); + } - private boolean useRestMatching(String path) { - // only need to do rest matching if using { } placeholders - return path.indexOf('{') > -1; + return answer; } /** @@ -57,7 +97,7 @@ public class JettyRestServletResolveConsumerStrategy extends HttpServletResolveC * @param consumerPath the consumer path which may use { } tokens * @return <tt>true</tt> if matched, <tt>false</tt> otherwise */ - public boolean matchRestPath(String requestPath, String consumerPath) { + public boolean matchRestPath(String requestPath, String consumerPath, boolean wildcard) { // remove starting/ending slashes if (requestPath.startsWith("/")) { requestPath = requestPath.substring(1); @@ -86,7 +126,7 @@ public class JettyRestServletResolveConsumerStrategy extends HttpServletResolveC String p1 = requestPaths[i]; String p2 = consumerPaths[i]; - if (p2.startsWith("{") && p2.endsWith("}")) { + if (wildcard && p2.startsWith("{") && p2.endsWith("}")) { // always matches continue; } @@ -100,4 +140,19 @@ public class JettyRestServletResolveConsumerStrategy extends HttpServletResolveC return true; } + /** + * Matches the given request HTTP method with the configured HTTP method of the consumer + * + * @param method the request HTTP method + * @param restrict the consumer configured HTTP restrict method + * @return <tt>true</tt> if matched, <tt>false</tt> otherwise + */ + public boolean matchRestMethod(String method, String restrict) { + if (restrict == null) { + return true; + } + + return method.toLowerCase(Locale.US).endsWith(restrict.toLowerCase(Locale.US)); + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/7ff40206/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestPathMatchingTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestPathMatchingTest.java b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestPathMatchingTest.java index df66449..5e0d726 100644 --- a/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestPathMatchingTest.java +++ b/components/camel-jetty/src/test/java/org/apache/camel/component/jetty/rest/RestPathMatchingTest.java @@ -24,32 +24,62 @@ public class RestPathMatchingTest extends TestCase { private JettyRestServletResolveConsumerStrategy matcher = new JettyRestServletResolveConsumerStrategy(); public void testRestPathMatcher() throws Exception { - assertTrue(matcher.matchRestPath("/foo/", "/foo/")); - assertTrue(matcher.matchRestPath("/foo/", "foo/")); - assertTrue(matcher.matchRestPath("/foo/", "foo")); - assertTrue(matcher.matchRestPath("foo/", "foo")); - assertTrue(matcher.matchRestPath("foo", "foo")); - assertTrue(matcher.matchRestPath("foo/", "foo")); - assertTrue(matcher.matchRestPath("/foo/", "foo")); - - assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2014")); - assertTrue(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2014")); - assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2014/")); - assertTrue(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2014/")); - assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/{user}/list/{year}")); - - assertFalse(matcher.matchRestPath("/foo/", "/bar/")); - assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2015")); - assertFalse(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2015")); - assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2015/")); - assertFalse(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2015/")); - assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/{user}/list/")); - - assertTrue(matcher.matchRestPath("/foo/1/list/2", "/foo/{user}/list/{year}")); - assertTrue(matcher.matchRestPath("/foo/1234567890/list/2", "/foo/{user}/list/{year}")); - assertTrue(matcher.matchRestPath("/foo/1234567890/list/1234567890", "/foo/{user}/list/{year}")); - - assertTrue(matcher.matchRestPath("/123/list/2014", "/{user}/list/{year}")); - assertTrue(matcher.matchRestPath("/1234567890/list/2014", "/{user}/list/{year}")); + assertTrue(matcher.matchRestPath("/foo/", "/foo/", true)); + assertTrue(matcher.matchRestPath("/foo/", "foo/", true)); + assertTrue(matcher.matchRestPath("/foo/", "foo", true)); + assertTrue(matcher.matchRestPath("foo/", "foo", true)); + assertTrue(matcher.matchRestPath("foo", "foo", true)); + assertTrue(matcher.matchRestPath("foo/", "foo", true)); + assertTrue(matcher.matchRestPath("/foo/", "foo", true)); + + assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2014", true)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2014", true)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2014/", true)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2014/", true)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/{user}/list/{year}", true)); + + assertFalse(matcher.matchRestPath("/foo/", "/bar/", true)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2015", true)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2015", true)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2015/", true)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2015/", true)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/{user}/list/", true)); + + assertTrue(matcher.matchRestPath("/foo/1/list/2", "/foo/{user}/list/{year}", true)); + assertTrue(matcher.matchRestPath("/foo/1234567890/list/2", "/foo/{user}/list/{year}", true)); + assertTrue(matcher.matchRestPath("/foo/1234567890/list/1234567890", "/foo/{user}/list/{year}", true)); + + assertTrue(matcher.matchRestPath("/123/list/2014", "/{user}/list/{year}", true)); + assertTrue(matcher.matchRestPath("/1234567890/list/2014", "/{user}/list/{year}", true)); + } + + public void testRestPathMatcherNoWildcard() throws Exception { + assertTrue(matcher.matchRestPath("/foo/", "/foo/", false)); + assertTrue(matcher.matchRestPath("/foo/", "foo/", false)); + assertTrue(matcher.matchRestPath("/foo/", "foo", false)); + assertTrue(matcher.matchRestPath("foo/", "foo", false)); + assertTrue(matcher.matchRestPath("foo", "foo", false)); + assertTrue(matcher.matchRestPath("foo/", "foo", false)); + assertTrue(matcher.matchRestPath("/foo/", "foo", false)); + + assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2014", false)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2014", false)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2014/", false)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2014/", false)); + assertTrue(matcher.matchRestPath("/foo/1234/list/2014", "/foo/{user}/list/{year}", true)); + + assertFalse(matcher.matchRestPath("/foo/", "/bar/", false)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2015", false)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2015", false)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/1234/list/2015/", false)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014/", "/foo/1234/list/2015/", false)); + assertFalse(matcher.matchRestPath("/foo/1234/list/2014", "/foo/{user}/list/", false)); + + assertFalse(matcher.matchRestPath("/foo/1/list/2", "/foo/{user}/list/{year}", false)); + assertFalse(matcher.matchRestPath("/foo/1234567890/list/2", "/foo/{user}/list/{year}", false)); + assertFalse(matcher.matchRestPath("/foo/1234567890/list/1234567890", "/foo/{user}/list/{year}", false)); + + assertFalse(matcher.matchRestPath("/123/list/2014", "/{user}/list/{year}", false)); + assertFalse(matcher.matchRestPath("/1234567890/list/2014", "/{user}/list/{year}", false)); } } http://git-wip-us.apache.org/repos/asf/camel/blob/7ff40206/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestServletResolveConsumerStrategy.java ---------------------------------------------------------------------- diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestServletResolveConsumerStrategy.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestServletResolveConsumerStrategy.java index 7a5a57b..ac24ef0 100644 --- a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestServletResolveConsumerStrategy.java +++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestServletResolveConsumerStrategy.java @@ -16,9 +16,16 @@ */ package org.apache.camel.component.servlet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; +import org.apache.camel.component.bean.MethodInfo; import org.apache.camel.component.http.HttpConsumer; import org.apache.camel.component.http.HttpServletResolveConsumerStrategy; @@ -29,24 +36,61 @@ public class ServletRestServletResolveConsumerStrategy extends HttpServletResolv @Override public HttpConsumer resolve(HttpServletRequest request, Map<String, HttpConsumer> consumers) { + HttpConsumer answer = null; + String path = request.getPathInfo(); if (path == null) { return null; } + String method = request.getMethod(); + if (method == null) { + return null; + } - for (String key : consumers.keySet()) { - if (useRestMatching(key) && matchRestPath(path, key)) { - return consumers.get(key); + List<HttpConsumer> candidates = new ArrayList<HttpConsumer>(); + + // first match by http method + for (Map.Entry<String, HttpConsumer> entry : consumers.entrySet()) { + String restrict = entry.getValue().getEndpoint().getHttpMethodRestrict(); + if (matchRestMethod(method, restrict)) { + candidates.add(entry.getValue()); } } - // fallback to default - return super.resolve(request, consumers); - } + // then see if we got a direct match + Iterator<HttpConsumer> it = candidates.iterator(); + while (it.hasNext()) { + HttpConsumer consumer = it.next(); + String consumerPath = consumer.getPath(); + if (matchRestPath(path, consumerPath, false)) { + answer = consumer; + break; + } + } + + // then match by non wildcard path + if (answer == null) { + it = candidates.iterator(); + while (it.hasNext()) { + HttpConsumer consumer = it.next(); + String consumerPath = consumer.getPath(); + if (!matchRestPath(path, consumerPath, true)) { + it.remove(); + } + } + + // there should only be one + if (candidates.size() == 1) { + answer = candidates.get(0); + } + } + + if (answer == null) { + // fallback to default + answer = super.resolve(request, consumers); + } - private boolean useRestMatching(String path) { - // only need to do rest matching if using { } placeholders - return path.indexOf('{') > -1; + return answer; } /** @@ -56,7 +100,7 @@ public class ServletRestServletResolveConsumerStrategy extends HttpServletResolv * @param consumerPath the consumer path which may use { } tokens * @return <tt>true</tt> if matched, <tt>false</tt> otherwise */ - public boolean matchRestPath(String requestPath, String consumerPath) { + public boolean matchRestPath(String requestPath, String consumerPath, boolean wildcard) { // remove starting/ending slashes if (requestPath.startsWith("/")) { requestPath = requestPath.substring(1); @@ -85,7 +129,7 @@ public class ServletRestServletResolveConsumerStrategy extends HttpServletResolv String p1 = requestPaths[i]; String p2 = consumerPaths[i]; - if (p2.startsWith("{") && p2.endsWith("}")) { + if (wildcard && p2.startsWith("{") && p2.endsWith("}")) { // always matches continue; } @@ -99,4 +143,19 @@ public class ServletRestServletResolveConsumerStrategy extends HttpServletResolv return true; } + /** + * Matches the given request HTTP method with the configured HTTP method of the consumer + * + * @param method the request HTTP method + * @param restrict the consumer configured HTTP restrict method + * @return <tt>true</tt> if matched, <tt>false</tt> otherwise + */ + public boolean matchRestMethod(String method, String restrict) { + if (restrict == null) { + return true; + } + + return method.toLowerCase(Locale.US).endsWith(restrict.toLowerCase(Locale.US)); + } + } http://git-wip-us.apache.org/repos/asf/camel/blob/7ff40206/examples/camel-example-servlet-rest-tomcat/src/main/java/org/apache/camel/example/rest/UserRouteBuilder.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-servlet-rest-tomcat/src/main/java/org/apache/camel/example/rest/UserRouteBuilder.java b/examples/camel-example-servlet-rest-tomcat/src/main/java/org/apache/camel/example/rest/UserRouteBuilder.java index 46280cd..7b4729a 100644 --- a/examples/camel-example-servlet-rest-tomcat/src/main/java/org/apache/camel/example/rest/UserRouteBuilder.java +++ b/examples/camel-example-servlet-rest-tomcat/src/main/java/org/apache/camel/example/rest/UserRouteBuilder.java @@ -36,14 +36,14 @@ public class UserRouteBuilder extends RouteBuilder { // this user REST service is json only rest("/user").consumes("application/json").produces("application/json") - .get("/view/{id}").outType(User.class) + .get("/{id}").outType(User.class) .to("bean:userService?method=getUser(${header.id})") - .get("/list").outTypeList(User.class) - .to("bean:userService?method=listUsers") + .put().type(User.class).outType(User.class) + .to("bean:userService?method=updateUser") - .put("/update").type(User.class).outType(User.class) - .to("bean:userService?method=updateUser"); + .get("/findAll").outTypeList(User.class) + .to("bean:userService?method=listUsers"); } } http://git-wip-us.apache.org/repos/asf/camel/blob/7ff40206/examples/camel-example-servlet-rest-tomcat/src/main/resources/camel-config-xml.xml ---------------------------------------------------------------------- diff --git a/examples/camel-example-servlet-rest-tomcat/src/main/resources/camel-config-xml.xml b/examples/camel-example-servlet-rest-tomcat/src/main/resources/camel-config-xml.xml index ccb9a15..0ef2fcc 100755 --- a/examples/camel-example-servlet-rest-tomcat/src/main/resources/camel-config-xml.xml +++ b/examples/camel-example-servlet-rest-tomcat/src/main/resources/camel-config-xml.xml @@ -38,20 +38,20 @@ <rest uri="/user" consumes="application/json" produces="application/json"> <!-- this is a rest GET to view an user by the given id --> - <get uri="/view/{id}" outType="org.apache.camel.example.rest.User"> + <get uri="/{id}" outType="org.apache.camel.example.rest.User"> <to uri="bean:userService?method=getUser(${header.id})"/> </get> - <!-- this is a rest GET to view all users --> - <get uri="/list" outList="true" outType="org.apache.camel.example.rest.User"> - <to uri="bean:userService?method=listUsers"/> - </get> - <!-- this is a rest PUT to create/update an user --> - <put uri="/update" type="org.apache.camel.example.rest.User" outType="org.apache.camel.example.rest.User"> + <put type="org.apache.camel.example.rest.User" outType="org.apache.camel.example.rest.User"> <to uri="bean:userService?method=updateUser"/> </put> + <!-- this is a rest GET to find all users --> + <get uri="/findAll" outList="true" outType="org.apache.camel.example.rest.User"> + <to uri="bean:userService?method=listUsers"/> + </get> + </rest> </camelContext> http://git-wip-us.apache.org/repos/asf/camel/blob/7ff40206/examples/camel-example-servlet-rest-tomcat/src/main/webapp/index.html ---------------------------------------------------------------------- diff --git a/examples/camel-example-servlet-rest-tomcat/src/main/webapp/index.html b/examples/camel-example-servlet-rest-tomcat/src/main/webapp/index.html index beccdc1..e48aed4 100644 --- a/examples/camel-example-servlet-rest-tomcat/src/main/webapp/index.html +++ b/examples/camel-example-servlet-rest-tomcat/src/main/webapp/index.html @@ -33,25 +33,25 @@ And for XML DSL the routes are define in XML code, in the <tt>src/main/resources <p/> There is a <i>user</i> REST service that supports the following operations <ul> - <li>view/<i>id</i> - to view a user with the given id </li> - <li>list - to view all users</li> - <li>update - to update/create an user</li> + <li>GET /user/{id} - to view a user with the given id </li> + <li>GET /user/final - to view all users</li> + <li>PUT /user - to update/create an user</li> </ul> -The view and list operations are HTTP GET, and update is using HTTP PUT. +The view operations are HTTP GET, and update is using HTTP PUT. From a web browser you can access the first two services using the following links <ul> - <li><a href="rest/user/view/123">user/view/123</a> - to view the user with id 123</li> - <li><a href="rest/user/list">user/list</a> - to list all users</li> + <li><a href="rest/user/123">user/123</a> - to view the user with id 123</li> + <li><a href="rest/user/findAll">user/findAll</a> - to list all users</li> </ul> From the command shell you can use curl to access the service as shown below: <pre> - curl -X GET -H "Accept: application/json" http://localhost:8080/camel-example-servlet-rest-tomcat-{version}/rest/user/view/123 - curl -X GET -H "Accept: application/json" http://localhost:8080/camel-example-servlet-rest-tomcat-{version}/rest/user/list - curl -X PUT -d "{ \"id\": 666, \"name\": \"The devil\"}" -H "Accept: application/json" http://localhost:8080/camel-example-servlet-rest-tomcat-{version}/rest/user/update + curl -X GET -H "Accept: application/json" http://localhost:8080/camel-example-servlet-rest-tomcat-{version}/rest/user/123 + curl -X GET -H "Accept: application/json" http://localhost:8080/camel-example-servlet-rest-tomcat-{version}/rest/user/findAll + curl -X PUT -d "{ \"id\": 666, \"name\": \"The devil\"}" -H "Accept: application/json" http://localhost:8080/camel-example-servlet-rest-tomcat-{version}/rest/user </pre> This assume you installed the example by copying the .war as <tt>camel-example-servlet-rest-tomcat-VERSION.war</tt>