This is an automated email from the ASF dual-hosted git repository. distomin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new 932ab9a CAMEL-13429: Support expressions in path parameters at REST DSL and REST producer (#3120) 932ab9a is described below commit 932ab9a1632c83f21c6f68a0c922ee5a791ac372 Author: Denis Istomin <istomin....@gmail.com> AuthorDate: Sat Aug 24 01:45:22 2019 +0500 CAMEL-13429: Support expressions in path parameters at REST DSL and REST producer (#3120) --- .../apache/camel/component/rest/RestProducer.java | 42 ++++- .../apache/camel/model/rest/RestDefinition.java | 10 +- .../rest/FromRestGetPlaceholderParamTest.java | 95 ++++++++++ .../camel/component/rest/RestProducerPathTest.java | 193 +++++++++++++++++++++ docs/user-manual/modules/ROOT/pages/rest-dsl.adoc | 13 +- 5 files changed, 339 insertions(+), 14 deletions(-) diff --git a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java index 168d2d5..2fe5950 100644 --- a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java +++ b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java @@ -155,15 +155,15 @@ public class RestProducer extends DefaultAsyncProducer { String[] arr = resolvedUriTemplate.split("\\/"); CollectionStringBuffer csb = new CollectionStringBuffer("/"); for (String a : arr) { - if (a.startsWith("{") && a.endsWith("}")) { - String key = a.substring(1, a.length() - 1); - String value = inMessage.getHeader(key, String.class); - if (value != null) { - hasPath = true; - csb.append(value); - } else { - csb.append(a); - } + String resolvedUriParam = resolveHeaderPlaceholders(a, inMessage); + + // Backward compatibility: if one of the path params is fully resolved, + // then it is assumed that whole uri is resolved. + if (!a.equals(resolvedUriParam) + && !resolvedUriParam.contains("{") + && !resolvedUriParam.contains("}")) { + hasPath = true; + csb.append(resolvedUriParam); } else { csb.append(a); } @@ -223,6 +223,30 @@ public class RestProducer extends DefaultAsyncProducer { } } + /** + * Replaces placeholders "{}" with message header values + * @param str string with placeholders + * @param msg message with headers + * @return filled string + */ + private String resolveHeaderPlaceholders(String str, Message msg) { + int startIndex = -1; + String res = str; + while ((startIndex = res.indexOf("{", startIndex + 1)) >= 0) { + int endIndex = res.indexOf("}", startIndex); + if (endIndex == -1) { + continue; + } + String key = res.substring(startIndex + 1, endIndex); + String headerValue = msg.getHeader(key, String.class); + if (headerValue != null) { + res = res.substring(0, startIndex) + headerValue + res.substring(endIndex + 1); + } + } + + return res; + } + @Override protected void doStart() throws Exception { super.doStart(); diff --git a/core/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java b/core/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java index 0bf0d8e..8e25341 100644 --- a/core/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java +++ b/core/camel-core/src/main/java/org/apache/camel/model/rest/RestDefinition.java @@ -23,6 +23,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -917,9 +919,11 @@ public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition> } catch (Exception e) { throw RuntimeCamelException.wrapRuntimeCamelException(e); } - if (a.startsWith("{") && a.endsWith("}")) { - String key = a.substring(1, a.length() - 1); - // merge if exists + + Matcher m = Pattern.compile("\\{(.*?)\\}").matcher(a); + while (m.find()) { + String key = m.group(1); + // merge if exists boolean found = false; for (RestOperationParamDefinition param : verb.getParams()) { // name is mandatory diff --git a/core/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetPlaceholderParamTest.java b/core/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetPlaceholderParamTest.java new file mode 100644 index 0000000..a3b47f9 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/component/rest/FromRestGetPlaceholderParamTest.java @@ -0,0 +1,95 @@ +/* + * 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.rest; + +import java.util.List; + +import javax.naming.Context; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.ToDefinition; +import org.apache.camel.model.rest.RestDefinition; +import org.apache.camel.model.rest.RestOperationParamDefinition; +import org.apache.camel.model.rest.RestParamType; +import org.junit.Test; + +public class FromRestGetPlaceholderParamTest extends ContextTestSupport { + + @Override + protected Context createJndiContext() throws Exception { + Context context = super.createJndiContext(); + context.bind("dummy-rest", new DummyRestConsumerFactory()); + return context; + } + + @Test + public void testFromRestModelSingleParam() { + RestDefinition rest = context.getRestDefinitions().get(0); + assertNotNull(rest); + assertEquals("items/", rest.getPath()); + assertEquals(1, rest.getVerbs().size()); + ToDefinition to = assertIsInstanceOf(ToDefinition.class, rest.getVerbs().get(0).getTo()); + assertEquals("direct:hello", to.getUri()); + + // Validate params + List<RestOperationParamDefinition> paramDefinitions = rest.getVerbs().get(0).getParams(); + assertEquals(1, paramDefinitions.size()); + assertEquals(RestParamType.path, paramDefinitions.get(0).getType()); + assertEquals("id", paramDefinitions.get(0).getName()); + } + + @Test + public void testFromRestModelMultipleParams() { + RestDefinition rest = context.getRestDefinitions().get(1); + assertNotNull(rest); + assertEquals("items/", rest.getPath()); + assertEquals(1, rest.getVerbs().size()); + ToDefinition to = assertIsInstanceOf(ToDefinition.class, rest.getVerbs().get(0).getTo()); + assertEquals("direct:hello", to.getUri()); + + // Validate params + List<RestOperationParamDefinition> paramDefinitions = rest.getVerbs().get(0).getParams(); + assertEquals(3, paramDefinitions.size()); + assertEquals(RestParamType.path, paramDefinitions.get(0).getType()); + assertEquals("id", paramDefinitions.get(0).getName()); + assertEquals(RestParamType.path, paramDefinitions.get(1).getType()); + assertEquals("filename", paramDefinitions.get(1).getName()); + assertEquals(RestParamType.path, paramDefinitions.get(2).getType()); + assertEquals("content-type", paramDefinitions.get(2).getName()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() { + restConfiguration().host("localhost"); + rest("items/") + .get("/{id}") + .to("direct:hello"); + + rest("items/") + .get("{id}/{filename}.{content-type}") + .to("direct:hello"); + + from("direct:hello") + .transform().constant("Hello World"); + } + }; + } +} diff --git a/core/camel-core/src/test/java/org/apache/camel/component/rest/RestProducerPathTest.java b/core/camel-core/src/test/java/org/apache/camel/component/rest/RestProducerPathTest.java new file mode 100644 index 0000000..52e9c5b --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/component/rest/RestProducerPathTest.java @@ -0,0 +1,193 @@ +/* + * 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.rest; + +import java.util.HashMap; + +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.Assert; +import org.junit.Test; + +public class RestProducerPathTest { + private final RestComponent restComponent; + + public RestProducerPathTest() { + DefaultCamelContext context = new DefaultCamelContext(); + context.addComponent("mock-rest", new RestEndpointTest.MockRest()); + + restComponent = new RestComponent(); + restComponent.setCamelContext(context); + } + + private RestProducer createProducer(String uri) throws Exception { + final RestEndpoint restEndpoint = (RestEndpoint) restComponent.createEndpoint(uri); + restEndpoint.setConsumerComponentName("mock-rest"); + restEndpoint.setParameters(new HashMap<>()); + restEndpoint.setHost("http://localhost"); + restEndpoint.setBindingMode("json"); + + return (RestProducer) restEndpoint.createProducer(); + } + + @Test + public void testEmptyParam() throws Exception { + RestProducer producer = createProducer("rest:get:list//{id}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertEquals("http://localhost/list//1", actual); + } + + @Test + public void testNoHeaders() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id}_{val}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertNull(actual); + } + + @Test + public void testMissingHeader() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id}/{val}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + // Backward compatibility: if one of the params is resolved + Assert.assertEquals("http://localhost/list/1/{val}", actual); + } + + @Test + public void testMissingHeaderSingleParam() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id}_{val}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertNull(actual); + } + + @Test + public void testMissingStartCurlyBrace() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id}_val}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + message.setHeader("val", "test"); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertNull(actual); + } + + @Test + public void testSingleMissingStartCurlyBrace() throws Exception { + RestProducer producer = createProducer("rest:get:list/id}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertNull(actual); + } + + @Test + public void testSingleMissingEndCurlyBrace() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertNull(actual); + } + + @Test + public void testMissingEndCurlyBrace() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id_{val}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + message.setHeader("val", "test"); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertNull(actual); + } + + @Test + public void testSingleParam() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertEquals("http://localhost/list/1", actual); + } + + @Test + public void testUnderscoreSeparator() throws Exception { + RestProducer producer = createProducer("rest:get:list/{id}_{val}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("id", 1); + message.setHeader("val", "test"); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertEquals("http://localhost/list/1_test", actual); + } + + @Test + public void testDotSeparator() throws Exception { + RestProducer producer = createProducer("rest:get:items/item.{content-type}"); + Exchange exchange = producer.createExchange(); + Message message = exchange.getIn(); + message.setHeader("content-type", "xml"); + + producer.process(exchange); + + String actual = (String) message.getHeader(Exchange.REST_HTTP_URI); + Assert.assertEquals("http://localhost/items/item.xml", actual); + } +} diff --git a/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc b/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc index 33a165b..4b080e9 100644 --- a/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc +++ b/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc @@ -19,7 +19,7 @@ others that has native REST integration. The following Camel components supports the Rest DSL. See the bottom of this page for how to integrate a component with the Rest DSL. -* xref:components::rest-component.adoc[came-rest] *required* contains the base rest component needed by Rest DSL +* xref:components::rest-component.adoc[camel-rest] *required* contains the base rest component needed by Rest DSL * xref:components::netty-http-component.adoc[camel-netty-http] (also supports Swagger Java) * xref:components::jetty-component.adoc[camel-jetty] (also @@ -149,7 +149,7 @@ And using XML DSL it becomes: </rest> ---- -TIP:The REST DSL will take care of duplicate path separators when using base +TIP: The REST DSL will take care of duplicate path separators when using base path and uri templates. In the example above the rest base path ends with a slash ( / ) and the verb starts with a slash ( / ). But Apache Camel will take care of this and remove the duplicated slash. @@ -173,6 +173,15 @@ only. The example above can be defined as: </rest> ---- +TIP: You can combine path parameters to build complex expressions. +For example: +[source,java] +---- + rest("items/") + .get("{id}/{filename}.{content-type}") + .to("direct:item") +---- + == Using Dynamic To in Rest DSL *Available as of Camel 2.16*