CAMEL-9988: Create an Etcd based ServiceCall EIP

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

Branch: refs/heads/master
Commit: bafe28e477d6bc3419fba3a5806d6cd09fe86e58
Parents: dbfcd60
Author: lburgazzoli <lburgazz...@gmail.com>
Authored: Tue Jun 7 10:29:19 2016 +0200
Committer: lburgazzoli <lburgazz...@gmail.com>
Committed: Tue Jun 7 16:30:43 2016 +0200

----------------------------------------------------------------------
 .../remote/DefaultServiceCallProcessor.java     |  12 +-
 .../DefaultServiceCallProcessorFactory.java     |  22 ++--
 .../remote/EtcdConfigurationDefinition.java     | 125 +++++++++++++++++++
 .../model/remote/ServiceCallDefinition.java     |   9 ++
 .../org/apache/camel/model/remote/jaxb.index    |   1 +
 .../blueprint/CamelContextFactoryBean.java      |   1 +
 components/camel-etcd/pom.xml                   |  15 +++
 .../component/etcd/AbstractEtcdEndpoint.java    |  33 +----
 .../camel/component/etcd/EtcdComponent.java     |   2 +-
 .../camel/component/etcd/EtcdConfiguration.java |  58 +++++++++
 .../apache/camel/component/etcd/EtcdHelper.java |   9 ++
 .../processor/remote/EtcdProcessorFactory.java  |  24 ----
 .../remote/EtcdServiceCallProcessor.java        |  43 +++++++
 .../remote/EtcdServiceCallProcessorFactory.java |  68 ++++++++++
 .../processor/remote/EtcdServiceCallServer.java |  59 +++++++++
 .../EtcdServiceCallServerListStrategies.java    |  83 ++++++++++++
 .../EtcdServiceCallServerListStrategy.java      |  74 +++++++++++
 .../apache/camel/model/ServiceCallDefinition    |  18 +++
 .../camel/component/etcd/EtcdKeysTest.java      |   2 +-
 .../camel/component/etcd/EtcdStatsTest.java     |   2 +-
 .../apache/camel/component/etcd/EtcdTest.java   |  45 -------
 .../camel/component/etcd/EtcdTestSupport.java   |  45 +++++++
 .../camel/component/etcd/EtcdWatchTest.java     |   2 +-
 .../remote/EtcdServiceCallRouteTest.java        | 117 +++++++++++++++++
 .../camel/spring/CamelContextFactoryBean.java   |   5 +-
 .../spring/handler/CamelNamespaceHandler.java   |  14 +--
 26 files changed, 757 insertions(+), 131 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessor.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessor.java
 
b/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessor.java
index 31b145c..9340406 100644
--- 
a/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessor.java
+++ 
b/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessor.java
@@ -234,12 +234,12 @@ public class DefaultServiceCallProcessor<S extends 
ServiceCallServer> extends Se
             List<S> servers = 
serverListStrategy.getUpdatedListOfServers(serviceName);
             if (servers == null || servers.isEmpty()) {
                 exchange.setException(new RejectedExecutionException("No 
active services with name " + name));
-            }
-
-            // let the client load balancer chose which server to use
-            server = servers.size() > 1 ?  loadBalancer.chooseServer(servers) 
: servers.get(0);
-            if (server == null) {
-                exchange.setException(new RejectedExecutionException("No 
active services with name " + name));
+            } else {
+                // let the client load balancer chose which server to use
+                server = servers.size() > 1 ? 
loadBalancer.chooseServer(servers) : servers.get(0);
+                if (server == null) {
+                    exchange.setException(new RejectedExecutionException("No 
active services with name " + name));
+                }
             }
         } catch (Throwable e) {
             exchange.setException(e);

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessorFactory.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessorFactory.java
 
b/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessorFactory.java
index 0cdc0f4..d74023e 100644
--- 
a/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessorFactory.java
+++ 
b/camel-core/src/main/java/org/apache/camel/impl/remote/DefaultServiceCallProcessorFactory.java
@@ -84,16 +84,16 @@ public abstract class DefaultServiceCallProcessorFactory<C, 
S extends ServiceCal
             throw new IllegalStateException("The ServiceCall: " + definition + 
" must be configured before it can be used.");
         }
 
-        // extract the properties from the configuration from the model
-        Map<String, Object> parameters = new HashMap<>();
-        if (configRef != null) {
-            IntrospectionSupport.getProperties(configRef, parameters, null);
-        }
-        if (config != null) {
-            IntrospectionSupport.getProperties(config, parameters, null);
-        }
-
         if (cfg != null) {
+            // extract the properties from the configuration from the model
+            Map<String, Object> parameters = new HashMap<>();
+            if (configRef != null) {
+                IntrospectionSupport.getProperties(configRef, parameters, 
null);
+            }
+            if (config != null) {
+                IntrospectionSupport.getProperties(config, parameters, null);
+            }
+
             IntrospectionSupport.setProperties(cfg, parameters);
         }
 
@@ -139,10 +139,10 @@ public abstract class 
DefaultServiceCallProcessorFactory<C, S extends ServiceCal
         Map<String, String> properties = configureProperties(routeContext, 
config, configRef);
 
         DefaultServiceCallProcessor processor = createProcessor(name, 
component, uri, mep, cfg, properties);
-        if (sl != null && processor.getServerListStrategy() != null) {
+        if (sl != null && processor.getServerListStrategy() == null) {
             processor.setServerListStrategy(sl);
         }
-        if (lb != null && processor.getLoadBalancer() != null) {
+        if (lb != null && processor.getLoadBalancer() == null) {
             processor.setLoadBalancer(lb);
         }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/camel-core/src/main/java/org/apache/camel/model/remote/EtcdConfigurationDefinition.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/model/remote/EtcdConfigurationDefinition.java
 
b/camel-core/src/main/java/org/apache/camel/model/remote/EtcdConfigurationDefinition.java
new file mode 100644
index 0000000..1edd5f0
--- /dev/null
+++ 
b/camel-core/src/main/java/org/apache/camel/model/remote/EtcdConfigurationDefinition.java
@@ -0,0 +1,125 @@
+/**
+ * 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.model.remote;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.camel.spi.Metadata;
+
+/**
+ * Etcd remote service call configuration
+ */
+@Metadata(label = "eip,routing,remote")
+@XmlRootElement(name = "etcdConfiguration")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class EtcdConfigurationDefinition extends 
ServiceCallConfigurationDefinition {
+    @XmlAttribute
+    private String uris;
+    @XmlAttribute @Metadata(label = "security")
+    private String userName;
+    @XmlAttribute @Metadata(label = "security")
+    private String password;
+    @XmlAttribute
+    private Long timeout;
+    @XmlAttribute @Metadata(defaultValue = "/services/")
+    private String servicePath = "/services/";
+
+    public EtcdConfigurationDefinition() {
+    }
+
+    public EtcdConfigurationDefinition(ServiceCallDefinition parent) {
+        super(parent);
+    }
+
+    // 
-------------------------------------------------------------------------
+    // Getter/Setter
+    // 
-------------------------------------------------------------------------
+
+    public String getUris() {
+        return uris;
+    }
+
+    public void setUris(String uris) {
+        this.uris = uris;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public Long getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(Long timeout) {
+        this.timeout = timeout;
+    }
+
+    public String getServicePath() {
+        return servicePath;
+    }
+
+    public void setServicePath(String servicePath) {
+        this.servicePath = servicePath;
+    }
+
+
+    // 
-------------------------------------------------------------------------
+    // Fluent API
+    // 
-------------------------------------------------------------------------
+
+    public EtcdConfigurationDefinition uris(String uris) {
+        setUris(uris);
+        return this;
+    }
+
+    public EtcdConfigurationDefinition userName(String userName) {
+        setUserName(userName);
+        return this;
+    }
+
+    public EtcdConfigurationDefinition password(String password) {
+        setPassword(password);
+        return this;
+    }
+
+    public EtcdConfigurationDefinition timeout(Long timeout) {
+        setTimeout(timeout);
+        return this;
+    }
+
+    public EtcdConfigurationDefinition servicePath(String servicePath) {
+        setServicePath(servicePath);
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/camel-core/src/main/java/org/apache/camel/model/remote/ServiceCallDefinition.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/model/remote/ServiceCallDefinition.java
 
b/camel-core/src/main/java/org/apache/camel/model/remote/ServiceCallDefinition.java
index ed1c2aa..a778b96 100644
--- 
a/camel-core/src/main/java/org/apache/camel/model/remote/ServiceCallDefinition.java
+++ 
b/camel-core/src/main/java/org/apache/camel/model/remote/ServiceCallDefinition.java
@@ -135,6 +135,15 @@ public class ServiceCallDefinition extends 
NoOutputDefinition<ServiceCallDefinit
     }
 
     /**
+     * Configures the Service Call EIP using Etcd
+     * <p/>
+     * Use <tt>end</tt> when configuration is complete, to return back to the 
Service Call EIP.
+     */
+    public EtcdConfigurationDefinition etcdConfiguration() {
+        serviceCallConfiguration = new EtcdConfigurationDefinition(this);
+        return (EtcdConfigurationDefinition) serviceCallConfiguration;
+    }
+    /**
      * Configures the ServiceCall using the given configuration
      */
     public ServiceCallDefinition 
serviceCallConfiguration(ServiceCallConfigurationDefinition configuration) {

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/camel-core/src/main/resources/org/apache/camel/model/remote/jaxb.index
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/resources/org/apache/camel/model/remote/jaxb.index 
b/camel-core/src/main/resources/org/apache/camel/model/remote/jaxb.index
index 6d7d250..8100bd3 100644
--- a/camel-core/src/main/resources/org/apache/camel/model/remote/jaxb.index
+++ b/camel-core/src/main/resources/org/apache/camel/model/remote/jaxb.index
@@ -15,6 +15,7 @@
 ## limitations under the License.
 ## ------------------------------------------------------------------------
 ConsulConfigurationDefinition
+EtcdConfigurationDefinition
 KubernetesConfigurationDefinition
 RibbonConfigurationDefinition
 ServiceCallDefinition

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
----------------------------------------------------------------------
diff --git 
a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
 
b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
index 1ca4507..dda9a98 100644
--- 
a/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
+++ 
b/components/camel-blueprint/src/main/java/org/apache/camel/blueprint/CamelContextFactoryBean.java
@@ -147,6 +147,7 @@ public class CamelContextFactoryBean extends 
AbstractCamelContextFactoryBean<Blu
         @XmlElement(name = "kubernetesConfiguration", type = 
KubernetesConfigurationDefinition.class, required = false),
         @XmlElement(name = "ribbonConfiguration", type = 
RibbonConfigurationDefinition.class, required = false),
         @XmlElement(name = "consulConfiguration", type = 
RibbonConfigurationDefinition.class, required = false),
+        @XmlElement(name = "etcdConfiguration", type = 
RibbonConfigurationDefinition.class, required = false),
         @XmlElement(name = "template", type = 
CamelProducerTemplateFactoryBean.class, required = false),
         @XmlElement(name = "consumerTemplate", type = 
CamelConsumerTemplateFactoryBean.class, required = false),
         @XmlElement(name = "proxy", type = CamelProxyFactoryBean.class, 
required = false),

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/pom.xml
----------------------------------------------------------------------
diff --git a/components/camel-etcd/pom.xml b/components/camel-etcd/pom.xml
index 1868850..4e307b9 100644
--- a/components/camel-etcd/pom.xml
+++ b/components/camel-etcd/pom.xml
@@ -83,6 +83,21 @@
       <artifactId>camel-test</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-test-spring</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-http</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-jetty</artifactId>
+      <scope>test</scope>
+    </dependency>
 
   </dependencies>
 

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/AbstractEtcdEndpoint.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/AbstractEtcdEndpoint.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/AbstractEtcdEndpoint.java
index 6847d85..423d482 100644
--- 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/AbstractEtcdEndpoint.java
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/AbstractEtcdEndpoint.java
@@ -16,11 +16,7 @@
  */
 package org.apache.camel.component.etcd;
 
-import java.net.URI;
-import javax.net.ssl.SSLContext;
-
 import mousio.etcd4j.EtcdClient;
-import mousio.etcd4j.EtcdSecurityContext;
 import org.apache.camel.impl.DefaultEndpoint;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -68,33 +64,6 @@ public abstract class AbstractEtcdEndpoint extends 
DefaultEndpoint {
     }
 
     public EtcdClient createClient() throws Exception {
-        String[] uris;
-        if (configuration.getUris() != null) {
-            uris = configuration.getUris().split(",");
-        } else {
-            uris = EtcdConstants.ETCD_DEFAULT_URIS.split(",");
-        }
-
-        URI[] etcdUriList = new URI[uris.length];
-
-        int i = 0;
-        for (String uri : uris) {
-            etcdUriList[i++] = 
URI.create(getCamelContext().resolvePropertyPlaceholders(uri));
-        }
-
-        return new EtcdClient(
-            new EtcdSecurityContext(
-                createSslContext(configuration),
-                configuration.getUserName(),
-                configuration.getPassword()),
-            etcdUriList
-        );
-    }
-
-    private SSLContext createSslContext(EtcdConfiguration configuration) 
throws Exception {
-        if (configuration.getSslContextParameters() != null) {
-            return 
configuration.getSslContextParameters().createSSLContext(getCamelContext());
-        }
-        return null;
+        return configuration.createClient();
     }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdComponent.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdComponent.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdComponent.java
index 3b81579..9c07b2a 100644
--- 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdComponent.java
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdComponent.java
@@ -49,7 +49,7 @@ public class EtcdComponent extends UriEndpointComponent {
         }
 
         EtcdNamespace namespace = 
getCamelContext().getTypeConverter().mandatoryConvertTo(EtcdNamespace.class, 
ns);
-        EtcdConfiguration configuration = loadConfiguration(new 
EtcdConfiguration(), parameters);
+        EtcdConfiguration configuration = loadConfiguration(new 
EtcdConfiguration(getCamelContext()), parameters);
 
         if (namespace != null) {
             // path must start with leading slash

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdConfiguration.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdConfiguration.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdConfiguration.java
index 5edad92..2f80e8d 100644
--- 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdConfiguration.java
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdConfiguration.java
@@ -16,6 +16,11 @@
  */
 package org.apache.camel.component.etcd;
 
+import java.net.URI;
+
+import mousio.etcd4j.EtcdClient;
+import mousio.etcd4j.EtcdSecurityContext;
+import org.apache.camel.CamelContext;
 import org.apache.camel.spi.UriParam;
 import org.apache.camel.spi.UriParams;
 import org.apache.camel.util.jsse.SSLContextParameters;
@@ -41,6 +46,18 @@ public class EtcdConfiguration {
     private Long timeout;
     @UriParam(label = "consumer,advanced", defaultValue = "0")
     private Long fromIndex = 0L;
+    @UriParam(defaultValue = "/services/")
+    private String servicePath = "/services/";
+
+    private final CamelContext camelContext;
+
+    public EtcdConfiguration(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    public CamelContext getCamelContext() {
+        return this.camelContext;
+    }
 
     public String getUris() {
         return uris;
@@ -123,6 +140,10 @@ public class EtcdConfiguration {
         return timeout;
     }
 
+    public boolean hasTimeout() {
+        return timeout != null && timeout > 0;
+    }
+
     /**
      * To set the maximum time an action could take to complete.
      */
@@ -140,4 +161,41 @@ public class EtcdConfiguration {
     public void setFromIndex(Long fromIndex) {
         this.fromIndex = fromIndex;
     }
+
+    public String getServicePath() {
+        return servicePath;
+    }
+
+    /**
+     * The path to look for for service discovery
+     */
+    public void setServicePath(String servicePath) {
+        this.servicePath = servicePath;
+    }
+
+    public EtcdClient createClient() throws Exception {
+        String[] uris;
+        if (getUris() != null) {
+            uris = getUris().split(",");
+        } else {
+            uris = EtcdConstants.ETCD_DEFAULT_URIS.split(",");
+        }
+
+        URI[] etcdUriList = new URI[uris.length];
+
+        int i = 0;
+        for (String uri : uris) {
+            etcdUriList[i++] = 
URI.create(camelContext.resolvePropertyPlaceholders(uri));
+        }
+
+        return new EtcdClient(
+            new EtcdSecurityContext(
+                sslContextParameters != null
+                    ? sslContextParameters.createSSLContext(camelContext)
+                    : null,
+                userName,
+                password),
+            etcdUriList
+        );
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdHelper.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdHelper.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdHelper.java
index c86e1a0..b222037 100644
--- 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdHelper.java
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/EtcdHelper.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.component.etcd;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import mousio.etcd4j.responses.EtcdErrorCode;
 import mousio.etcd4j.responses.EtcdException;
 
@@ -32,4 +35,10 @@ public final class EtcdHelper  {
 
         return false;
     }
+
+    public static ObjectMapper createObjectMapper() {
+        return new ObjectMapper()
+            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
false)
+            .setSerializationInclusion(JsonInclude.Include.NON_NULL);
+    }
 }

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdProcessorFactory.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdProcessorFactory.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdProcessorFactory.java
deleted file mode 100644
index 0e60a77..0000000
--- 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdProcessorFactory.java
+++ /dev/null
@@ -1,24 +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.etcd.processor.remote;
-
-/**
- * @author lburgazzoli
- */
-public class EtcdProcessorFactory {
-}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessor.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessor.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessor.java
new file mode 100644
index 0000000..3251ae5
--- /dev/null
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessor.java
@@ -0,0 +1,43 @@
+/**
+ * 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.etcd.processor.remote;
+
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.component.etcd.EtcdConfiguration;
+import org.apache.camel.impl.remote.DefaultServiceCallProcessor;
+import org.apache.camel.spi.ProcessorFactory;
+import org.apache.camel.spi.ServiceCallServer;
+import org.apache.camel.spi.ServiceCallServerListStrategy;
+
+/**
+ * {@link ProcessorFactory} that creates the Etcd implementation of the 
ServiceCall EIP.
+ */
+public class EtcdServiceCallProcessor extends 
DefaultServiceCallProcessor<ServiceCallServer> {
+    public EtcdServiceCallProcessor(String name, String scheme, String uri, 
ExchangePattern exchangePattern, EtcdConfiguration conf) {
+        super(name, scheme, uri, exchangePattern);
+    }
+
+    @Override
+    public void 
setServerListStrategy(ServiceCallServerListStrategy<ServiceCallServer> 
serverListStrategy) {
+        if (!(serverListStrategy instanceof 
EtcdServiceCallServerListStrategy)) {
+            throw new IllegalArgumentException("ServerListStrategy is not an 
instance of EtcdServiceCallServerListStrategy");
+        }
+
+        super.setServerListStrategy(serverListStrategy);
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessorFactory.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessorFactory.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessorFactory.java
new file mode 100644
index 0000000..9f25738
--- /dev/null
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallProcessorFactory.java
@@ -0,0 +1,68 @@
+/**
+ * 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.etcd.processor.remote;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.component.etcd.EtcdConfiguration;
+import org.apache.camel.impl.remote.DefaultServiceCallProcessor;
+import org.apache.camel.impl.remote.DefaultServiceCallProcessorFactory;
+import org.apache.camel.spi.ProcessorFactory;
+import org.apache.camel.spi.RouteContext;
+import org.apache.camel.spi.ServiceCallServer;
+import org.apache.camel.spi.ServiceCallServerListStrategy;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * {@link ProcessorFactory} that creates the Etcd implementation of the 
ServiceCall EIP.
+ */
+public class EtcdServiceCallProcessorFactory extends 
DefaultServiceCallProcessorFactory<EtcdConfiguration, ServiceCallServer> {
+    @Override
+    protected EtcdConfiguration createConfiguration(RouteContext routeContext) 
throws Exception {
+        return new EtcdConfiguration(routeContext.getCamelContext());
+    }
+
+    @Override
+    protected DefaultServiceCallProcessor createProcessor(
+            String name,
+            String component,
+            String uri,
+            ExchangePattern mep,
+            EtcdConfiguration conf,
+            Map<String, String> properties) throws Exception {
+
+        return new EtcdServiceCallProcessor(name, component, uri, mep, conf);
+    }
+
+    @Override
+    protected Optional<ServiceCallServerListStrategy> 
builtInServerListStrategy(EtcdConfiguration conf, String name) throws Exception 
{
+        ServiceCallServerListStrategy strategy = null;
+        if (ObjectHelper.equal("ondemand", name, true)) {
+            strategy = new EtcdServiceCallServerListStrategies.OnDemand(conf);
+        }
+
+        return Optional.ofNullable(strategy);
+    }
+
+    @Override
+    protected ServiceCallServerListStrategy<ServiceCallServer> 
createDefaultServerListStrategy(EtcdConfiguration conf) throws Exception {
+        return new EtcdServiceCallServerListStrategies.OnDemand(conf);
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServer.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServer.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServer.java
new file mode 100644
index 0000000..d5c25d9
--- /dev/null
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServer.java
@@ -0,0 +1,59 @@
+/**
+ * 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.etcd.processor.remote;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.camel.impl.remote.DefaultServiceCallServer;
+
+public class EtcdServiceCallServer extends DefaultServiceCallServer {
+    public static final Comparator<EtcdServiceCallServer> COMPARATOR = 
comparator();
+
+    private final String name;
+    private final Map<String, String> tags;
+
+    @JsonCreator
+    public EtcdServiceCallServer(
+        @JsonProperty("name") final String name,
+        @JsonProperty("address") final String address,
+        @JsonProperty("port") final Integer port,
+        @JsonProperty("tags") final Map<String, String> tags) {
+        super(address, port);
+
+        this.name = name;
+        this.tags = Collections.unmodifiableMap(tags != null ? tags : 
Collections.EMPTY_MAP);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Map<String, String> getTags() {
+        return tags;
+    }
+
+    public static Comparator<EtcdServiceCallServer> comparator() {
+        Comparator<EtcdServiceCallServer> byAddress = (e1, e2) -> 
e2.getIp().compareTo(e1.getIp());
+        Comparator<EtcdServiceCallServer> byPort = (e1, e2) -> 
Integer.compare(e2.getPort(), e1.getPort());
+
+        return byAddress.thenComparing(byPort);
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategies.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategies.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategies.java
new file mode 100644
index 0000000..e4b4a8a
--- /dev/null
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategies.java
@@ -0,0 +1,83 @@
+/**
+ * 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.etcd.processor.remote;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import mousio.etcd4j.requests.EtcdKeyGetRequest;
+import mousio.etcd4j.responses.EtcdKeysResponse;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.etcd.EtcdConfiguration;
+import org.apache.camel.spi.ServiceCallServer;
+import org.apache.camel.util.ObjectHelper;
+
+public final class EtcdServiceCallServerListStrategies {
+    private EtcdServiceCallServerListStrategies() {
+    }
+
+    public static final class OnDemand extends 
EtcdServiceCallServerListStrategy {
+        public OnDemand(EtcdConfiguration configuration) throws Exception {
+            super(configuration);
+        }
+
+        @Override
+        public List<ServiceCallServer> getUpdatedListOfServers(String name) {
+            List<ServiceCallServer> servers = Collections.emptyList();
+            try {
+                final EtcdConfiguration conf = getConfiguration();
+                final EtcdKeyGetRequest request = 
getClient().get(conf.getServicePath()).recursive();
+                if (conf.hasTimeout()) {
+                    request.timeout(conf.getTimeout(), TimeUnit.SECONDS);
+                }
+
+                final EtcdKeysResponse response = request.send().get();
+
+                if (Objects.nonNull(response.node) && 
!response.node.nodes.isEmpty()) {
+                    servers = response.node.nodes.stream()
+                        .map(node -> node.value)
+                        .filter(ObjectHelper::isNotEmpty)
+                        .map(this::nodeFromString)
+                        .filter(Objects::nonNull)
+                        .filter(s -> name.equalsIgnoreCase(s.getName()))
+                        .sorted(EtcdServiceCallServer.COMPARATOR)
+                        .collect(Collectors.toList());
+                }
+            } catch (Exception e) {
+                throw new RuntimeCamelException(e);
+            }
+
+            return servers;
+        }
+
+        @Override
+        public String toString() {
+            return "OnDemand";
+        }
+    }
+
+    // 
*************************************************************************
+    // Helpers
+    // 
*************************************************************************
+
+    public static EtcdServiceCallServerListStrategy onDemand(EtcdConfiguration 
configuration) throws Exception {
+        return new OnDemand(configuration);
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategy.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategy.java
 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategy.java
new file mode 100644
index 0000000..38f6139
--- /dev/null
+++ 
b/components/camel-etcd/src/main/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallServerListStrategy.java
@@ -0,0 +1,74 @@
+/**
+ * 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.etcd.processor.remote;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import mousio.etcd4j.EtcdClient;
+import org.apache.camel.component.etcd.EtcdConfiguration;
+import org.apache.camel.component.etcd.EtcdHelper;
+import org.apache.camel.impl.remote.DefaultServiceCallServerListStrategy;
+import org.apache.camel.spi.ServiceCallServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EtcdServiceCallServerListStrategy extends 
DefaultServiceCallServerListStrategy<ServiceCallServer> {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(EtcdServiceCallServerListStrategy.class);
+    private static final ObjectMapper MAPPER = EtcdHelper.createObjectMapper();
+
+    private final EtcdConfiguration configuration;
+    private EtcdClient client;
+
+    public EtcdServiceCallServerListStrategy(EtcdConfiguration configuration) {
+        this.configuration = configuration;
+        this.client = null;
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        if (client == null) {
+            client = configuration.createClient();
+        }
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        if (client != null) {
+            client.close();
+            client = null;
+        }
+    }
+
+    protected EtcdConfiguration getConfiguration() {
+        return this.configuration;
+    }
+
+    protected EtcdClient getClient() {
+        return this.client;
+    }
+
+    protected EtcdServiceCallServer nodeFromString(String value) {
+        EtcdServiceCallServer server = null;
+
+        try {
+            server = MAPPER.readValue(value, EtcdServiceCallServer.class);
+        } catch (Exception e) {
+            LOGGER.warn("", e);
+        }
+
+        return server;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/main/resources/META-INF/services/org/apache/camel/model/ServiceCallDefinition
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/main/resources/META-INF/services/org/apache/camel/model/ServiceCallDefinition
 
b/components/camel-etcd/src/main/resources/META-INF/services/org/apache/camel/model/ServiceCallDefinition
new file mode 100644
index 0000000..0cd6c42
--- /dev/null
+++ 
b/components/camel-etcd/src/main/resources/META-INF/services/org/apache/camel/model/ServiceCallDefinition
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+class=org.apache.camel.component.etcd.processor.remote.EtcdServiceCallProcessorFactory

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdKeysTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdKeysTest.java
 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdKeysTest.java
index cf16017..c2bc507 100644
--- 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdKeysTest.java
+++ 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdKeysTest.java
@@ -30,7 +30,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.Test;
 
 //@Ignore("Etcd must be started manually")
-public class EtcdKeysTest extends EtcdTest {
+public class EtcdKeysTest extends EtcdTestSupport {
 
     @Test(expected = EtcdException.class)
     public void testKeys() throws Exception {

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdStatsTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdStatsTest.java
 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdStatsTest.java
index aba6045..b3eeb13 100644
--- 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdStatsTest.java
+++ 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdStatsTest.java
@@ -26,7 +26,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.Test;
 
 //@Ignore("Etcd must be started manually")
-public class EtcdStatsTest extends EtcdTest {
+public class EtcdStatsTest extends EtcdTestSupport {
 
     @Test
     public void testStats() throws Exception {

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTest.java
 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTest.java
deleted file mode 100644
index a8100e6..0000000
--- 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTest.java
+++ /dev/null
@@ -1,45 +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.etcd;
-
-import java.net.URI;
-
-import mousio.etcd4j.EtcdClient;
-import mousio.etcd4j.responses.EtcdKeysResponse;
-import org.apache.camel.Exchange;
-import org.apache.camel.Processor;
-import org.apache.camel.test.junit4.CamelTestSupport;
-
-public class EtcdTest extends CamelTestSupport {
-    protected static final Processor NODE_TO_VALUE_IN = new Processor() {
-        @Override
-        public void process(Exchange exchange) throws Exception {
-            EtcdKeysResponse response = 
exchange.getIn().getBody(EtcdKeysResponse.class);
-            if (response != null) {
-                exchange.getIn().setBody(response.node.key + "=" + 
response.node.value);
-            }
-        }
-    };
-
-    public boolean isCreateCamelContextPerClass() {
-        return false;
-    }
-
-    protected EtcdClient getClient() {
-        return new EtcdClient(URI.create("http://localhost:4001";));
-    }
-}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTestSupport.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTestSupport.java
 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTestSupport.java
new file mode 100644
index 0000000..e66466d
--- /dev/null
+++ 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdTestSupport.java
@@ -0,0 +1,45 @@
+/**
+ * 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.etcd;
+
+import java.net.URI;
+
+import mousio.etcd4j.EtcdClient;
+import mousio.etcd4j.responses.EtcdKeysResponse;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.test.junit4.CamelTestSupport;
+
+public class EtcdTestSupport extends CamelTestSupport {
+    protected static final Processor NODE_TO_VALUE_IN = new Processor() {
+        @Override
+        public void process(Exchange exchange) throws Exception {
+            EtcdKeysResponse response = 
exchange.getIn().getBody(EtcdKeysResponse.class);
+            if (response != null) {
+                exchange.getIn().setBody(response.node.key + "=" + 
response.node.value);
+            }
+        }
+    };
+
+    public boolean isCreateCamelContextPerClass() {
+        return false;
+    }
+
+    protected EtcdClient getClient() {
+        return new EtcdClient(URI.create("http://localhost:2379";));
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdWatchTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdWatchTest.java
 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdWatchTest.java
index c511325..39aca62 100644
--- 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdWatchTest.java
+++ 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/EtcdWatchTest.java
@@ -24,7 +24,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.Test;
 
 //@Ignore("Etcd must be started manually")
-public class EtcdWatchTest extends EtcdTest {
+public class EtcdWatchTest extends EtcdTestSupport {
 
     @Test
     public void testWatchWithPath() throws Exception {

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallRouteTest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallRouteTest.java
 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallRouteTest.java
new file mode 100644
index 0000000..a20bd02
--- /dev/null
+++ 
b/components/camel-etcd/src/test/java/org/apache/camel/component/etcd/processor/remote/EtcdServiceCallRouteTest.java
@@ -0,0 +1,117 @@
+/**
+ * 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.etcd.processor.remote;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import mousio.etcd4j.EtcdClient;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.etcd.EtcdHelper;
+import org.apache.camel.component.etcd.EtcdTestSupport;
+import org.junit.Ignore;
+import org.junit.Test;
+
+@Ignore
+public class EtcdServiceCallRouteTest extends EtcdTestSupport {
+    private static final ObjectMapper MAPPER = EtcdHelper.createObjectMapper();
+    private static final String SERVICE_NAME = "http-service";
+    private static final int SERVICE_COUNT = 5;
+    private static final int SERVICE_PORT_BASE = 8080;
+
+    private EtcdClient client;
+    private List<Map<String, Object>> servers;
+    private List<String> expectedBodies;
+
+    // 
*************************************************************************
+    // Setup / tear down
+    // 
*************************************************************************
+
+    @Override
+    protected void doPreSetup() throws Exception {
+        client = getClient();
+
+        servers = new ArrayList<>(SERVICE_COUNT);
+        expectedBodies = new ArrayList<>(SERVICE_COUNT);
+
+        for (int i = 0; i < SERVICE_COUNT; i++) {
+            Map<String, Object> server = new HashMap<>();
+            server.put("name", SERVICE_NAME);
+            server.put("address", "127.0.0.1");
+            server.put("port", SERVICE_PORT_BASE + i);
+
+            client.put("/services/" + "service-" + i, 
MAPPER.writeValueAsString(server)).send().get();
+
+            servers.add(Collections.unmodifiableMap(server));
+            expectedBodies.add("ping on " + (SERVICE_PORT_BASE + i));
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        client.deleteDir("/services/").recursive().send().get();
+    }
+
+    // 
*************************************************************************
+    // Test
+    // 
*************************************************************************
+
+    @Test
+    public void testServiceCall() throws Exception {
+        getMockEndpoint("mock:result").expectedMessageCount(SERVICE_COUNT);
+        
getMockEndpoint("mock:result").expectedBodiesReceivedInAnyOrder(expectedBodies);
+
+        servers.forEach(s -> template.sendBody("direct:start", "ping"));
+
+        assertMockEndpointsSatisfied();
+    }
+
+    // 
*************************************************************************
+    // Route
+    // 
*************************************************************************
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                    .serviceCall()
+                        .name(SERVICE_NAME)
+                        .etcdConfiguration()
+                            .component("http")
+                            .loadBalancer("roundrobin")
+                            .serverListStrategy("ondemand")
+                        .end()
+                    
.to("log:org.apache.camel.component.etcd.processor.service?level=INFO&showAll=true&multiline=true")
+                    .to("mock:result");
+
+                servers.forEach(s ->
+                    fromF("jetty:http://%s:%d";, s.get("address"), 
s.get("port"))
+                        .transform().simple("${in.body} on " + s.get("port"))
+                );
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
----------------------------------------------------------------------
diff --git 
a/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
 
b/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
index 9f48921..7527881 100644
--- 
a/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
+++ 
b/components/camel-spring/src/main/java/org/apache/camel/spring/CamelContextFactoryBean.java
@@ -19,7 +19,6 @@ package org.apache.camel.spring;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
@@ -58,6 +57,7 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.ThreadPoolProfileDefinition;
 import org.apache.camel.model.dataformat.DataFormatsDefinition;
 import org.apache.camel.model.remote.ConsulConfigurationDefinition;
+import org.apache.camel.model.remote.EtcdConfigurationDefinition;
 import org.apache.camel.model.remote.KubernetesConfigurationDefinition;
 import org.apache.camel.model.remote.RibbonConfigurationDefinition;
 import org.apache.camel.model.rest.RestConfigurationDefinition;
@@ -155,7 +155,8 @@ public class CamelContextFactoryBean extends 
AbstractCamelContextFactoryBean<Spr
     private CamelJMXAgentDefinition camelJMXAgent;
     @XmlElements({
             @XmlElement(name = "hystrixConfiguration", type = 
HystrixConfigurationDefinition.class, required = false),
-            @XmlElement(name = "consulConfiguration", type = 
KubernetesConfigurationDefinition.class, required = false),
+            @XmlElement(name = "consulConfiguration", type = 
ConsulConfigurationDefinition.class, required = false),
+            @XmlElement(name = "etcdConfiguration", type = 
EtcdConfigurationDefinition.class, required = false),
             @XmlElement(name = "kubernetesConfiguration", type = 
KubernetesConfigurationDefinition.class, required = false),
             @XmlElement(name = "ribbonConfiguration", type = 
RibbonConfigurationDefinition.class, required = false),
             @XmlElement(name = "template", type = 
CamelProducerTemplateFactoryBean.class, required = false),

http://git-wip-us.apache.org/repos/asf/camel/blob/bafe28e4/components/camel-spring/src/main/java/org/apache/camel/spring/handler/CamelNamespaceHandler.java
----------------------------------------------------------------------
diff --git 
a/components/camel-spring/src/main/java/org/apache/camel/spring/handler/CamelNamespaceHandler.java
 
b/components/camel-spring/src/main/java/org/apache/camel/spring/handler/CamelNamespaceHandler.java
index 7470126..eae9f61 100644
--- 
a/components/camel-spring/src/main/java/org/apache/camel/spring/handler/CamelNamespaceHandler.java
+++ 
b/components/camel-spring/src/main/java/org/apache/camel/spring/handler/CamelNamespaceHandler.java
@@ -21,17 +21,10 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-
 import javax.xml.bind.Binder;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
 import org.apache.camel.builder.xml.Namespaces;
 import org.apache.camel.core.xml.CamelJMXAgentDefinition;
 import org.apache.camel.core.xml.CamelPropertyPlaceholderDefinition;
@@ -41,6 +34,7 @@ import org.apache.camel.model.FromDefinition;
 import org.apache.camel.model.HystrixConfigurationDefinition;
 import org.apache.camel.model.SendDefinition;
 import org.apache.camel.model.remote.ConsulConfigurationDefinition;
+import org.apache.camel.model.remote.EtcdConfigurationDefinition;
 import org.apache.camel.model.remote.KubernetesConfigurationDefinition;
 import org.apache.camel.model.remote.RibbonConfigurationDefinition;
 import org.apache.camel.spi.CamelContextNameStrategy;
@@ -71,6 +65,11 @@ import 
org.springframework.beans.factory.parsing.BeanComponentDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
 import org.springframework.beans.factory.xml.ParserContext;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 /**
  * Camel namespace for the spring XML configuration file.
@@ -148,6 +147,7 @@ public class CamelNamespaceHandler extends 
NamespaceHandlerSupport {
         addBeanDefinitionParser("propertyPlaceholder", 
CamelPropertyPlaceholderDefinition.class, false, false);
         addBeanDefinitionParser("hystrixConfiguration", 
HystrixConfigurationDefinition.class, false, false);
         addBeanDefinitionParser("consulConfiguration", 
ConsulConfigurationDefinition.class, false, false);
+        addBeanDefinitionParser("etcdConfiguration", 
EtcdConfigurationDefinition.class, false, false);
         addBeanDefinitionParser("kubernetesConfiguration", 
KubernetesConfigurationDefinition.class, false, false);
         addBeanDefinitionParser("ribbonConfiguration", 
RibbonConfigurationDefinition.class, false, false);
 

Reply via email to