CAMEL-7354: Rest DSL. Integrate with camel-servlet.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/036f6cbb Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/036f6cbb Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/036f6cbb Branch: refs/heads/master Commit: 036f6cbb1e49e92c44d3114807e5b3b44b3e14cc Parents: 61a1a2a Author: Claus Ibsen <davscl...@apache.org> Authored: Mon Jul 28 15:44:24 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Mon Jul 28 15:44:24 2014 +0200 ---------------------------------------------------------------------- .../component/jetty/JettyHttpComponent.java | 32 ++---- .../camel/component/jetty/JettyRestFilter.java | 47 --------- .../servlet/CamelHttpTransportServlet.java | 3 + .../component/servlet/ServletComponent.java | 54 +++++++++- .../servlet/ServletRestHttpBinding.java | 67 ++++++++++++ ...rvletRestServletResolveConsumerStrategy.java | 102 +++++++++++++++++++ .../servlet/rest/RestServletGetTest.java | 66 ++++++++++++ 7 files changed, 300 insertions(+), 71 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java index 1aee4be..757264d 100644 --- a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java +++ b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyHttpComponent.java @@ -377,23 +377,16 @@ public class JettyHttpComponent extends HttpComponent implements RestConsumerFac ServletContextHandler context = server.getChildHandlerByClass(ServletContextHandler.class); List<Filter> filters = endpoint.getFilters(); for (Filter filter : filters) { - if (filter instanceof JettyRestFilter) { - // special for rest filter - FilterHolder filterHolder = new FilterHolder(); - filterHolder.setFilter(new CamelFilterWrapper(filter)); - addFilter(context, filterHolder, "*"); - } else { - FilterHolder filterHolder = new FilterHolder(); - filterHolder.setFilter(new CamelFilterWrapper(filter)); - String pathSpec = endpoint.getPath(); - if (pathSpec == null || "".equals(pathSpec)) { - pathSpec = "/"; - } - if (endpoint.isMatchOnUriPrefix()) { - pathSpec = pathSpec.endsWith("/") ? pathSpec + "*" : pathSpec + "/*"; - } - addFilter(context, filterHolder, pathSpec); + FilterHolder filterHolder = new FilterHolder(); + filterHolder.setFilter(new CamelFilterWrapper(filter)); + String pathSpec = endpoint.getPath(); + if (pathSpec == null || "".equals(pathSpec)) { + pathSpec = "/"; + } + if (endpoint.isMatchOnUriPrefix()) { + pathSpec = pathSpec.endsWith("/") ? pathSpec + "*" : pathSpec + "/*"; } + addFilter(context, filterHolder, pathSpec); } } @@ -995,13 +988,6 @@ public class JettyHttpComponent extends HttpComponent implements RestConsumerFac JettyHttpEndpoint endpoint = camelContext.getEndpoint(url, JettyHttpEndpoint.class); setProperties(endpoint, parameters); - // add our filter - //List<Filter> list = endpoint.getFilters(); - //if (list == null) { - // list = new ArrayList<Filter>(); - //} - //list.add(0, new JettyRestFilter()); - //endpoint.setFilters(list); // disable this filter as we want to use ours endpoint.setEnableMultipartFilter(false); // use the rest binding http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestFilter.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestFilter.java b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestFilter.java deleted file mode 100644 index 9dd612e..0000000 --- a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty/JettyRestFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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 java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - -public class JettyRestFilter implements Filter { - - // TODO: we may want to use a filter so we can reuse this for camel-servlet - // to have it match the request with list of accepted paths - - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // noop - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - filterChain.doFilter(servletRequest, servletResponse); - } - - @Override - public void destroy() { - // noop - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java ---------------------------------------------------------------------- diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java index f064136..733ea72 100644 --- a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java +++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/CamelHttpTransportServlet.java @@ -39,6 +39,9 @@ public class CamelHttpTransportServlet extends CamelServlet { public void init(ServletConfig config) throws ServletException { super.init(config); + // use rest enabled resolver in case we use rest + this.setServletResolveConsumerStrategy(new ServletRestServletResolveConsumerStrategy()); + String ignore = config.getInitParameter("ignoreDuplicateServletName"); Boolean bool = ObjectConverter.toBoolean(ignore); if (bool != null) { http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java index 789538c..0fbe4d6 100644 --- a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java +++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletComponent.java @@ -17,11 +17,16 @@ package org.apache.camel.component.servlet; import java.net.URI; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; +import org.apache.camel.CamelContext; +import org.apache.camel.Consumer; import org.apache.camel.Endpoint; +import org.apache.camel.Processor; import org.apache.camel.component.http.AuthMethod; import org.apache.camel.component.http.HttpBinding; import org.apache.camel.component.http.HttpClientConfigurer; @@ -29,13 +34,16 @@ import org.apache.camel.component.http.HttpComponent; import org.apache.camel.component.http.HttpConsumer; import org.apache.camel.component.http.HttpEndpoint; import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RestConsumerFactory; +import org.apache.camel.util.FileUtil; import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.URISupport; import org.apache.camel.util.UnsafeUriCharactersEncoder; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.params.HttpClientParams; -public class ServletComponent extends HttpComponent { +public class ServletComponent extends HttpComponent implements RestConsumerFactory { private String servletName = "CamelServlet"; private HttpRegistry httpRegistry; @@ -156,4 +164,48 @@ public class ServletComponent extends HttpComponent { } + @Override + public Consumer createConsumer(CamelContext camelContext, Processor processor, String verb, String path, + String consumes, String produces, Map<String, Object> parameters) throws Exception { + path = FileUtil.stripLeadingSeparator(path); + + // if no explicit port/host configured, then use port from rest configuration + RestConfiguration config = getCamelContext().getRestConfiguration(); + + Map<String, Object> map = new HashMap<String, Object>(); + // build query string, and append any endpoint configuration properties + if (config != null && (config.getComponent() == null || config.getComponent().equals("servlet"))) { + // setup endpoint options + if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) { + map.putAll(config.getEndpointProperties()); + } + } + + String query = URISupport.createQueryString(map); + + // servlet:///hello + String url = "servlet:///%s?httpMethodRestrict=%s"; + if (!query.isEmpty()) { + url = url + "?" + query; + } + + // must use upper case for restrict + String restrict = verb.toUpperCase(Locale.US); + + // get the endpoint + url = String.format(url, path, restrict); + ServletEndpoint endpoint = camelContext.getEndpoint(url, ServletEndpoint.class); + setProperties(endpoint, parameters); + + // use the rest binding + endpoint.setBinding(new ServletRestHttpBinding()); + + // configure consumer properties + Consumer consumer = endpoint.createConsumer(processor); + if (config != null && config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) { + setProperties(consumer, config.getConsumerProperties()); + } + + return consumer; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java new file mode 100644 index 0000000..add507a --- /dev/null +++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestHttpBinding.java @@ -0,0 +1,67 @@ +/** + * 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.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.camel.component.http.DefaultHttpBinding; +import org.apache.camel.component.http.HttpMessage; + +public class ServletRestHttpBinding extends DefaultHttpBinding { + + @Override + protected void populateRequestParameters(HttpServletRequest request, HttpMessage message) throws Exception { + super.populateRequestParameters(request, message); + + String path = request.getPathInfo(); + if (path == null) { + return; + } + + // in the endpoint the user may have defined rest {} placeholders + // so we need to map those placeholders with data from the incoming request context path + + ServletEndpoint endpoint = (ServletEndpoint) message.getExchange().getFromEndpoint(); + String consumerPath = endpoint.getPath(); + + if (useRestMatching(consumerPath)) { + + // split using single char / is optimized in the jdk + String[] paths = path.split("/"); + String[] consumerPaths = consumerPath.split("/"); + + for (int i = 0; i < consumerPaths.length; i++) { + if (paths.length < i) { + break; + } + String p1 = consumerPaths[i]; + if (p1.startsWith("{") && p1.endsWith("}")) { + String key = p1.substring(1, p1.length() - 1); + String value = paths[i]; + if (value != null) { + message.setHeader(key, value); + } + } + } + } + } + + private boolean useRestMatching(String path) { + // only need to do rest matching if using { } placeholders + return path.indexOf('{') > -1; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/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 new file mode 100644 index 0000000..7a5a57b --- /dev/null +++ b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/ServletRestServletResolveConsumerStrategy.java @@ -0,0 +1,102 @@ +/** + * 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.servlet; + +import java.util.Map; +import javax.servlet.http.HttpServletRequest; + +import org.apache.camel.component.http.HttpConsumer; +import org.apache.camel.component.http.HttpServletResolveConsumerStrategy; + +/** + * A {@link org.apache.camel.component.http.HttpServletResolveConsumerStrategy} that supports the Rest DSL. + */ +public class ServletRestServletResolveConsumerStrategy extends HttpServletResolveConsumerStrategy { + + @Override + public HttpConsumer resolve(HttpServletRequest request, Map<String, HttpConsumer> consumers) { + String path = request.getPathInfo(); + if (path == null) { + return null; + } + + for (String key : consumers.keySet()) { + if (useRestMatching(key) && matchRestPath(path, key)) { + return consumers.get(key); + } + } + + // fallback to default + return super.resolve(request, consumers); + } + + private boolean useRestMatching(String path) { + // only need to do rest matching if using { } placeholders + return path.indexOf('{') > -1; + } + + /** + * Matches the given request path with the configured consumer path + * + * @param requestPath the request path + * @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) { + // 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); + } + + // split using single char / is optimized in the jdk + String[] requestPaths = requestPath.split("/"); + String[] consumerPaths = consumerPath.split("/"); + + // must be same number of path's + if (requestPaths.length != consumerPaths.length) { + return false; + } + + for (int i = 0; i < requestPaths.length; i++) { + String p1 = requestPaths[i]; + String p2 = consumerPaths[i]; + + if (p2.startsWith("{") && p2.endsWith("}")) { + // always matches + continue; + } + + if (!p1.equals(p2)) { + return false; + } + } + + // assume matching + return true; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/036f6cbb/components/camel-servlet/src/test/java/org/apache/camel/component/servlet/rest/RestServletGetTest.java ---------------------------------------------------------------------- diff --git a/components/camel-servlet/src/test/java/org/apache/camel/component/servlet/rest/RestServletGetTest.java b/components/camel-servlet/src/test/java/org/apache/camel/component/servlet/rest/RestServletGetTest.java new file mode 100644 index 0000000..fc9611d --- /dev/null +++ b/components/camel-servlet/src/test/java/org/apache/camel/component/servlet/rest/RestServletGetTest.java @@ -0,0 +1,66 @@ +/** + * 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.servlet.rest; + +import com.meterware.httpunit.GetMethodWebRequest; +import com.meterware.httpunit.WebRequest; +import com.meterware.httpunit.WebResponse; +import com.meterware.servletunit.ServletUnitClient; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.servlet.ServletCamelRouterTestSupport; +import org.junit.Test; + +public class RestServletGetTest extends ServletCamelRouterTestSupport { + + @Test + public void testServletProducerGet() throws Exception { + WebRequest req = new GetMethodWebRequest(CONTEXT_URL + "/services/users/123/basic"); + ServletUnitClient client = newClient(); + client.setExceptionsThrownOnErrorStatus(false); + WebResponse response = client.getResponse(req); + + assertEquals(200, response.getResponseCode()); + + assertEquals("123;Donald Duck", response.getText()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // configure to use servlet on localhost + restConfiguration().component("servlet").host("localhost"); + + // use the rest DSL to define the rest services + rest("/users/") + .get("{id}/basic") + .route() + .to("mock:input") + .process(new Processor() { + public void process(Exchange exchange) throws Exception { + String id = exchange.getIn().getHeader("id", String.class); + exchange.getOut().setBody(id + ";Donald Duck"); + } + }); + } + }; + } + +}