You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by di...@apache.org on 2019/08/23 20:45:29 UTC

[camel] branch master updated: CAMEL-13429: Support expressions in path parameters at REST DSL and REST producer (#3120)

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 <is...@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*