CAMEL-7354: Rest DSL. Integrate with camel-netty-http.

Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/248f8cd7
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/248f8cd7
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/248f8cd7

Branch: refs/heads/master
Commit: 248f8cd74885521abe8e8f0456a4323d2733a79b
Parents: d58fd11
Author: Claus Ibsen <davscl...@apache.org>
Authored: Tue Jul 29 09:08:24 2014 +0200
Committer: Claus Ibsen <davscl...@apache.org>
Committed: Tue Jul 29 10:12:10 2014 +0200

----------------------------------------------------------------------
 .../netty/http/DefaultContextPathMatcher.java   |  4 +
 .../netty/http/NettyHttpComponent.java          | 86 ++++++++++++++++--
 .../netty/http/RestContextPathMatcher.java      | 95 ++++++++++++++++++++
 .../netty/http/RestNettyHttpBinding.java        | 91 +++++++++++++++++++
 .../HttpServerMultiplexChannelHandler.java      |  9 +-
 .../netty/http/rest/RestNettyHttpGetTest.java   | 56 ++++++++++++
 6 files changed, 331 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java
index 55bfb58..2efc357 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java
@@ -42,6 +42,10 @@ public class DefaultContextPathMatcher implements 
ContextPathMatcher {
         }
     }
 
+    public String getPath() {
+        return path;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {

http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
index 3732454..90f09f1 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
@@ -18,15 +18,22 @@ package org.apache.camel.component.netty.http;
 
 import java.net.URI;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 
+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.netty.NettyComponent;
 import org.apache.camel.component.netty.NettyConfiguration;
 import org.apache.camel.component.netty.NettyServerBootstrapConfiguration;
 import 
org.apache.camel.component.netty.http.handlers.HttpServerMultiplexChannelHandler;
 import org.apache.camel.spi.HeaderFilterStrategy;
 import org.apache.camel.spi.HeaderFilterStrategyAware;
+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.ServiceHelper;
 import org.apache.camel.util.URISupport;
@@ -37,7 +44,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Netty HTTP based component.
  */
-public class NettyHttpComponent extends NettyComponent implements 
HeaderFilterStrategyAware {
+public class NettyHttpComponent extends NettyComponent implements 
HeaderFilterStrategyAware, RestConsumerFactory {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(NettyHttpComponent.class);
 
@@ -53,7 +60,8 @@ public class NettyHttpComponent extends NettyComponent 
implements HeaderFilterSt
         super(NettyHttpEndpoint.class);
         setConfiguration(new NettyHttpConfiguration());
         setHeaderFilterStrategy(new NettyHttpHeaderFilterStrategy());
-        setNettyHttpBinding(new 
DefaultNettyHttpBinding(getHeaderFilterStrategy()));
+        // use the binding that supports Rest DSL
+        setNettyHttpBinding(new 
RestNettyHttpBinding(getHeaderFilterStrategy()));
     }
 
     @Override
@@ -102,12 +110,16 @@ public class NettyHttpComponent extends NettyComponent 
implements HeaderFilterSt
         NettyHttpEndpoint answer = new NettyHttpEndpoint(addressUri, this, 
config);
         answer.setTimer(getTimer());
 
-        // set component options on endpoint as defaults
-        // As the component's NettyHttpBinding could be override by the 
setHeaderFilterStrategy
-        // Here we just create a new DefaultNettyHttpBinding here
+        // must use a copy of the binding on the endpoint to avoid sharing 
same instance that can cause side-effects
         if (answer.getNettyHttpBinding() == null) {
-            DefaultNettyHttpBinding nettyHttpBinding = 
(DefaultNettyHttpBinding)getNettyHttpBinding();
-            answer.setNettyHttpBinding(nettyHttpBinding.copy());
+            Object binding = getNettyHttpBinding();
+            if (binding instanceof RestNettyHttpBinding) {
+                NettyHttpBinding copy = ((RestNettyHttpBinding) 
binding).copy();
+                answer.setNettyHttpBinding(copy);
+            } else if (binding instanceof DefaultNettyHttpBinding) {
+                NettyHttpBinding copy = ((DefaultNettyHttpBinding) 
binding).copy();
+                answer.setNettyHttpBinding(copy);
+            }
         }
         if (headerFilterStrategy != null) {
             answer.setHeaderFilterStrategy(headerFilterStrategy);
@@ -199,7 +211,65 @@ public class NettyHttpComponent extends NettyComponent 
implements HeaderFilterSt
         }
         return answer;
     }
-    
+
+    @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);
+
+        String scheme = "http";
+        String host = "0.0.0.0";
+        int port = 0;
+
+        // if no explicit port/host configured, then use port from rest 
configuration
+        RestConfiguration config = getCamelContext().getRestConfiguration();
+        if (config != null && (config.getComponent() == null || 
config.getComponent().equals("netty-http"))) {
+            if (config.getScheme() != null) {
+                scheme = config.getScheme();
+            }
+            if (config.getHost() != null) {
+                host = config.getHost();
+            }
+            int num = config.getPort();
+            if (num > 0) {
+                port = num;
+            }
+        }
+
+        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("netty-http"))) {
+            // setup endpoint options
+            if (config.getEndpointProperties() != null && 
!config.getEndpointProperties().isEmpty()) {
+                map.putAll(config.getEndpointProperties());
+            }
+        }
+
+        String query = URISupport.createQueryString(map);
+
+        String url = "netty-http:%s://%s:%s/%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, scheme, host, port, path, restrict);
+        NettyHttpEndpoint endpoint = camelContext.getEndpoint(url, 
NettyHttpEndpoint.class);
+        setProperties(endpoint, parameters);
+
+        // configure consumer properties
+        Consumer consumer = endpoint.createConsumer(processor);
+        if (config != null && config.getConsumerProperties() != null && 
!config.getConsumerProperties().isEmpty()) {
+            setProperties(consumer, config.getConsumerProperties());
+        }
+
+        return consumer;
+    }
+
     @Override
     protected void doStop() throws Exception {
         super.doStop();

http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.java
new file mode 100644
index 0000000..c48e815
--- /dev/null
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.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.netty.http;
+
+/**
+ * A {@link org.apache.camel.component.netty.http.ContextPathMatcher} that 
supports the Rest DSL.
+ */
+public class RestContextPathMatcher extends DefaultContextPathMatcher {
+
+    private final String rawPath;
+
+    public RestContextPathMatcher(String rawPath, String path, boolean 
matchOnUriPrefix) {
+        super(path, matchOnUriPrefix);
+        this.rawPath = rawPath;
+    }
+
+    @Override
+    public boolean matches(String target) {
+        if (useRestMatching(rawPath)) {
+            return matchRestPath(target, rawPath);
+        } else {
+            return super.matches(target);
+        }
+    }
+
+    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/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java
new file mode 100644
index 0000000..ea8c448
--- /dev/null
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java
@@ -0,0 +1,91 @@
+/**
+ * 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.netty.http;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.HeaderFilterStrategy;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+
+/**
+ * A {@link org.apache.camel.component.netty.http.NettyHttpBinding} that 
supports the Rest DSL.
+ */
+public class RestNettyHttpBinding extends DefaultNettyHttpBinding {
+
+    public RestNettyHttpBinding() {
+    }
+
+    public RestNettyHttpBinding(HeaderFilterStrategy headerFilterStrategy) {
+        super(headerFilterStrategy);
+    }
+
+    public RestNettyHttpBinding copy() {
+        try {
+            return (RestNettyHttpBinding)this.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeCamelException(e);
+        }
+    }
+
+    @Override
+    public void populateCamelHeaders(HttpRequest request, Map<String, Object> 
headers, Exchange exchange, NettyHttpConfiguration configuration) throws 
Exception {
+        super.populateCamelHeaders(request, headers, exchange, configuration);
+
+        String path = request.getUri();
+        if (path == null) {
+            return;
+        }
+
+        // skip the scheme/host/port etc, as we only want the context-path
+        URI uri = new URI(path);
+        path = uri.getPath();
+
+        // 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
+
+        String consumerPath = configuration.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) {
+                        NettyHttpHelper.appendHeader(headers, 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/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
index 801360c..aedd1e1 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java
@@ -25,6 +25,7 @@ import 
org.apache.camel.component.netty.http.ContextPathMatcher;
 import org.apache.camel.component.netty.http.DefaultContextPathMatcher;
 import org.apache.camel.component.netty.http.HttpServerConsumerChannelFactory;
 import org.apache.camel.component.netty.http.NettyHttpConsumer;
+import org.apache.camel.component.netty.http.RestContextPathMatcher;
 import org.apache.camel.util.UnsafeUriCharactersEncoder;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.ChannelHandler;
@@ -66,14 +67,18 @@ public class HttpServerMultiplexChannelHandler extends 
SimpleChannelUpstreamHand
     }
 
     public void addConsumer(NettyHttpConsumer consumer) {
+        String rawPath = consumer.getConfiguration().getPath();
         String path = pathAsKey(consumer.getConfiguration().getPath());
-        ContextPathMatcher matcher = new DefaultContextPathMatcher(path, 
consumer.getConfiguration().isMatchOnUriPrefix());
+        // use rest path matcher in case Rest DSL is in use
+        ContextPathMatcher matcher = new RestContextPathMatcher(rawPath, path, 
consumer.getConfiguration().isMatchOnUriPrefix());
         consumers.put(matcher, new HttpServerChannelHandler(consumer));
     }
 
     public void removeConsumer(NettyHttpConsumer consumer) {
+        String rawPath = consumer.getConfiguration().getPath();
         String path = pathAsKey(consumer.getConfiguration().getPath());
-        ContextPathMatcher matcher = new DefaultContextPathMatcher(path, 
consumer.getConfiguration().isMatchOnUriPrefix());
+        // use rest path matcher in case Rest DSL is in use
+        ContextPathMatcher matcher = new RestContextPathMatcher(rawPath, path, 
consumer.getConfiguration().isMatchOnUriPrefix());
         consumers.remove(matcher);
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java
 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java
new file mode 100644
index 0000000..d36db7f
--- /dev/null
+++ 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java
@@ -0,0 +1,56 @@
+/**
+ * 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.netty.http.rest;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.netty.http.BaseNettyTest;
+import org.junit.Test;
+
+public class RestNettyHttpGetTest extends BaseNettyTest {
+
+    @Test
+    public void testProducerGet() throws Exception {
+        String out = 
template.requestBody("netty-http:http://localhost:{{port}}/users/123/basic";, 
null, String.class);
+        assertEquals("123;Donald Duck", out);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                // configure to use netty-http on localhost with the given port
+                
restConfiguration().component("netty-http").host("localhost").port(getPort());
+
+                // 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");
+                            }
+                        });
+            }
+        };
+    }
+
+}

Reply via email to