tamaya-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anat...@apache.org
Subject [4/5] incubator-tamaya-sandbox git commit: Added missing files, synched Workspace.
Date Tue, 01 Nov 2016 23:47:26 GMT
Added missing files, synched Workspace.


Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/commit/358828fe
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/tree/358828fe
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/diff/358828fe

Branch: refs/heads/master
Commit: 358828fe4b0df808de74cceebe892c79716c171c
Parents: 6fa2b34
Author: anatole <anatole@apache.org>
Authored: Wed Nov 2 00:46:53 2016 +0100
Committer: anatole <anatole@apache.org>
Committed: Wed Nov 2 00:46:53 2016 +0100

----------------------------------------------------------------------
 camel/pom.xml                                   | 101 ++++
 .../camel/TamayaPropertiesComponent.java        |  78 +++
 .../camel/TamayaPropertyResolver.java           |  53 ++
 .../camel/TamayaPropertyResolverTest.java       | 116 +++++
 .../test/resources/META-INF/camelcontext.xml    |  52 ++
 .../META-INF/javaconfiguration.properties       |  19 +
 camel/src/test/resources/META-INF/routes.xml    |  39 ++
 consul/pom.xml                                  |  99 ++++
 .../apache/tamaya/consul/ConsulBackends.java    |  59 +++
 .../tamaya/consul/ConsulPropertySource.java     | 198 +++++++
 .../org.apache.tamaya.spi.PropertySource        |  19 +
 etcd/pom.xml                                    |  97 ++++
 .../org/apache/tamaya/etcd/EtcdAccessor.java    | 520 +++++++++++++++++++
 .../org/apache/tamaya/etcd/EtcdBackends.java    |  65 +++
 .../apache/tamaya/etcd/EtcdPropertySource.java  | 209 ++++++++
 .../org.apache.tamaya.spi.PropertySource        |  19 +
 .../apache/tamaya/etcd/EtcdAccessorTest.java    | 116 +++++
 .../tamaya/etcd/EtcdPropertySourceTest.java     |  74 +++
 .../tamaya/jodatime/DurationConverter.java      |  72 +++
 .../tamaya/jodatime/DurationConverterIT.java    |  51 ++
 management/pom.xml                              |  81 +++
 .../management/ConfigManagementSupport.java     | 128 +++++
 .../apache/tamaya/management/ManagedConfig.java | 110 ++++
 .../tamaya/management/ManagedConfigMBean.java   | 119 +++++
 .../src/main/resources/META-INF/beans.xml       |  24 +
 .../META-INF/javaconfiguration.properties       |  19 +
 ....apache.tamaya.management.ManagedConfigMBean |  19 +
 .../management/internal/ManagedConfigTest.java  | 118 +++++
 .../src/test/resources/META-INF/beans.xml       |  24 +
 metamodel/pom.xml                               | 123 +++++
 .../metamodel/ConfigurationContextBuilder.java  | 354 +++++++++++++
 .../org/apache/tamaya/metamodel/Context.java    | 130 +++++
 ...efaultRefreshablePropertySourceProvider.java |  72 +++
 .../metamodel/internal/FactoryManager.java      | 140 +++++
 .../tamaya/metamodel/internal/Refreshable.java  |  37 ++
 .../tamaya/metamodel/internal/SourceConfig.java | 233 +++++++++
 .../apache/tamaya/metamodel/package-info.java   |  22 +
 .../metamodel/spi/PropertySourceFactory.java    |  49 ++
 .../spi/PropertySourceProviderFactory.java      |  50 ++
 metamodel/src/test/resources/tamaya-config.json |  39 ++
 metamodel/src/test/resources/tamaya-config.xml  |  62 +++
 metamodel/src/test/resources/tamaya-config.yaml |  87 ++++
 osgi/pom.xml                                    | 103 ++++
 .../tamaya/integration/osgi/Activator.java      | 134 +++++
 .../integration/osgi/OSGIConfigRootMapper.java  |  36 ++
 .../osgi/OSGIEnhancedConfiguration.java         | 117 +++++
 .../integration/osgi/TamayaConfigAdminImpl.java | 196 +++++++
 .../osgi/TamayaConfigurationImpl.java           | 127 +++++
 .../META-INF/javaconfiguration.properties       |  18 +
 osgi/src/test/resources/arquillian.xml          |  27 +
 osgi/src/test/resources/felix.properties        |  23 +
 ...MetainfConfigPropertySourceProviderTest.java |  37 ++
 server/pom.xml                                  | 203 ++++++++
 .../apache/tamaya/server/ConfigServiceApp.java  |  77 +++
 .../tamaya/server/ConfigurationResource.java    | 310 +++++++++++
 .../apache/tamaya/server/VersionProperties.java |  68 +++
 .../apache/tamaya/server/spi/ScopeManager.java  |  84 +++
 .../apache/tamaya/server/spi/ScopeProvider.java |  40 ++
 .../META-INF/tamaya-server-version.properties   |  22 +
 server/src/main/resources/banner.txt            |  14 +
 server/src/main/resources/config-server.yml     |  31 ++
 .../tamaya/server/ConfigServiceAppTest.java     |  32 ++
 .../org/apache/tamaya/server/EtcdAccessor.java  | 519 ++++++++++++++++++
 .../tamaya/server/VersionPropertiesTest.java    |  46 ++
 ui/base/src/main/main5.iml                      |  11 +
 ui/events/pom.xml                               |  36 ++
 ui/events/src/main/main7.iml                    |  12 +
 ui/mutableconfig/src/main/main8.iml             |  12 +
 ui/pom.xml                                      |  46 ++
 validation/pom.xml                              | 112 ++++
 .../resources/META-INF/configmodel.properties   |  35 ++
 ...org.apache.tamaya.events.ConfigEventListener |  19 +
 .../java/test/model/TestConfigAccessor.java     |  45 ++
 .../resources/META-INF/configmodel.properties   |  96 ++++
 .../META-INF/javaconfiguration.properties       |  22 +
 ...org.apache.tamaya.model.spi.ModelProviderSpi |  19 +
 .../src/test/resources/examples/configmodel.ini |  76 +++
 .../test/resources/examples/configmodel.json    | 108 ++++
 .../resources/examples/configmodel.properties   |  96 ++++
 .../src/test/resources/examples/configmodel.xml |  97 ++++
 .../test/resources/examples/configmodel.yaml    | 106 ++++
 81 files changed, 7308 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/pom.xml
----------------------------------------------------------------------
diff --git a/camel/pom.xml b/camel/pom.xml
new file mode 100644
index 0000000..d07de87
--- /dev/null
+++ b/camel/pom.xml
@@ -0,0 +1,101 @@
+<!-- 
+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 current 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.tamaya.ext</groupId>
+        <artifactId>tamaya-sandbox</artifactId>
+        <version>0.3-incubating-SNAPSHOT</version>
+        <relativePath>..</relativePath>
+    </parent>
+
+    <artifactId>tamaya-camel</artifactId>
+    <name>Apache Tamaya Modules - Apache Camel Support</name>
+    <packaging>bundle</packaging>
+
+    <properties>
+        <camel.version>2.17.0</camel.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>prepare-agent</id>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.tamaya.integration.camel
+                        </Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>java-hamcrest</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.tamaya</groupId>
+            <artifactId>tamaya-core</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya</groupId>
+            <artifactId>tamaya-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya.ext</groupId>
+            <artifactId>tamaya-functions</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core</artifactId>
+            <version>${camel.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertiesComponent.java
----------------------------------------------------------------------
diff --git a/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertiesComponent.java b/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertiesComponent.java
new file mode 100644
index 0000000..8b776a5
--- /dev/null
+++ b/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertiesComponent.java
@@ -0,0 +1,78 @@
+/*
+ * 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.tamaya.integration.camel;
+
+import java.util.Properties;
+
+import org.apache.camel.component.properties.PropertiesComponent;
+import org.apache.tamaya.ConfigurationProvider;
+
+/**
+ * Default Camel PropertiesComponent that additionally has cfg and tamaya prefixes configured for resolution of
+ * entries from tamaya.
+ */
+public class TamayaPropertiesComponent extends PropertiesComponent{
+
+    /**
+     * Constructor similar to parent.
+     */
+    public TamayaPropertiesComponent(){
+        super();
+        addFunction(new TamayaPropertyResolver("tamaya"));
+        addFunction(new TamayaPropertyResolver("cfg"));
+        setTamayaOverrides(true);
+    }
+
+    /**
+     * Constructor similar to parent with additional locations.
+     * @param locations additional locations for Camel.  
+     */
+    public TamayaPropertiesComponent(String ... locations){
+        super(locations);
+        addFunction(new TamayaPropertyResolver("tamaya"));
+        addFunction(new TamayaPropertyResolver("cfg"));
+        setTamayaOverrides(true);
+    }
+
+    /**
+     * Constructor similar to parent with only one location.
+     * @param location addition location for Camel.
+     */
+    public TamayaPropertiesComponent(String location){
+        super(location);
+        addFunction(new TamayaPropertyResolver("tamaya"));
+        addFunction(new TamayaPropertyResolver("cfg"));
+        setTamayaOverrides(true);
+    }
+
+    /**
+     * Apply the current Tamaya properties (configuration) as override properties evaluated first by camel before
+     * evaluating other uris.
+     * @param enabled flag to define if tamaya values override everything else.
+     */
+    public void setTamayaOverrides(boolean enabled){
+        if(enabled){
+            final Properties props = new Properties();
+            props.putAll(ConfigurationProvider.getConfiguration().getProperties());
+            setOverrideProperties(props);
+        } else{
+            setOverrideProperties(null);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertyResolver.java
----------------------------------------------------------------------
diff --git a/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertyResolver.java b/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertyResolver.java
new file mode 100644
index 0000000..6b6ada9
--- /dev/null
+++ b/camel/src/main/java/org/apache/tamaya/integration/camel/TamayaPropertyResolver.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tamaya.integration.camel;
+
+import org.apache.camel.component.properties.PropertiesFunction;
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.ConfigurationProvider;
+
+import java.util.Objects;
+
+
+/**
+ * Implementation of the Camel Properties SPI using Tamaya configuration.
+ */
+public class TamayaPropertyResolver implements PropertiesFunction{
+
+    private final String prefix;
+
+    /**
+     * Creates a new instance.
+     * @param configPrefix the prefix to be registered for explicit resolution by this resolver function, not null.
+     */
+    public TamayaPropertyResolver(String configPrefix){
+        this.prefix = Objects.requireNonNull(configPrefix);
+    }
+
+    @Override
+    public String getName() {
+        return prefix;
+    }
+
+    @Override
+    public String apply(String remainder) {
+        Configuration config = ConfigurationProvider.getConfiguration();
+        return config.get(remainder);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/src/test/java/org/apache/tamaya/integration/camel/TamayaPropertyResolverTest.java
----------------------------------------------------------------------
diff --git a/camel/src/test/java/org/apache/tamaya/integration/camel/TamayaPropertyResolverTest.java b/camel/src/test/java/org/apache/tamaya/integration/camel/TamayaPropertyResolverTest.java
new file mode 100644
index 0000000..0cba47e
--- /dev/null
+++ b/camel/src/test/java/org/apache/tamaya/integration/camel/TamayaPropertyResolverTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.tamaya.integration.camel;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.ProxyBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.RoutesDefinition;
+import org.junit.Test;
+
+import java.io.InputStream;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for integration of Tamaya with Apache Camel using Java DSL and XML DSL.
+ */
+public class TamayaPropertyResolverTest {
+
+    @Test
+    public void testJavaDSLWithCfgResolution() throws Exception {
+        CamelContext camelContext = new DefaultCamelContext();
+        camelContext.addComponent("properties", new TamayaPropertiesComponent());
+        RouteBuilder builder = new RouteBuilder() {
+            public void configure() {
+                from("direct:hello").transform().simple("{{cfg:message}}");
+            }
+        };
+        camelContext.addRoutes(builder);
+        camelContext.start();
+        // test configuration is injected right...
+        Greeter proxy = new ProxyBuilder(camelContext).endpoint("direct:hello").build(Greeter.class);
+        String greetMessage = proxy.greet();
+        assertEquals("Good Bye from Apache Tamaya!", greetMessage);
+    }
+
+    @Test
+    public void testJavaDSLWithTamayaResolution() throws Exception {
+        CamelContext camelContext = new DefaultCamelContext();
+        camelContext.addComponent("properties", new TamayaPropertiesComponent());
+        RouteBuilder builder = new RouteBuilder() {
+            public void configure() {
+                from("direct:hello").transform().simple("{{tamaya:message}}");
+            }
+        };
+        camelContext.addRoutes(builder);
+        camelContext.start();
+        // test configuration is injected right...
+        Greeter proxy = new ProxyBuilder(camelContext).endpoint("direct:hello").build(Greeter.class);
+        String greetMessage = proxy.greet();
+        assertEquals("Good Bye from Apache Tamaya!", greetMessage);
+    }
+
+    @Test
+    public void testJavaDSLWithOverrideActive() throws Exception {
+        CamelContext camelContext = new DefaultCamelContext();
+        TamayaPropertiesComponent props = new TamayaPropertiesComponent();
+        props.setTamayaOverrides(true);
+        camelContext.addComponent("properties", props);
+        RouteBuilder builder = new RouteBuilder() {
+            public void configure() {
+                from("direct:hello").transform().simple("{{message}}");
+            }
+        };
+        camelContext.addRoutes(builder);
+        camelContext.start();
+        // test configuration is injected right...
+        Greeter proxy = new ProxyBuilder(camelContext).endpoint("direct:hello").build(Greeter.class);
+        String greetMessage = proxy.greet();
+        assertEquals("Good Bye from Apache Tamaya!", greetMessage);
+    }
+
+    @Test
+    public void testXmlDSL() throws Exception {
+        CamelContext camelContext = new DefaultCamelContext();
+        // This is normally done by the Spring implemented registry, we keep it simple here...
+        TamayaPropertiesComponent props = new TamayaPropertiesComponent();
+        props.setTamayaOverrides(true);
+        camelContext.addComponent("properties", props);
+        // Read routes from XML DSL
+        InputStream is = getClass().getResourceAsStream("/META-INF/routes.xml");
+        RoutesDefinition routes = camelContext.loadRoutesDefinition(is);
+        for(RouteDefinition def: routes.getRoutes()) {
+            camelContext.addRouteDefinition(def);
+        }
+        camelContext.start();
+        Greeter greeter = new ProxyBuilder(camelContext).endpoint("direct:hello1").build(Greeter.class);
+        assertEquals("Good Bye from Apache Tamaya!", greeter.greet());
+        greeter = new ProxyBuilder(camelContext).endpoint("direct:hello2").build(Greeter.class);
+        assertEquals("Good Bye from Apache Tamaya!", greeter.greet());
+        greeter = new ProxyBuilder(camelContext).endpoint("direct:hello3").build(Greeter.class);
+        assertEquals("Good Bye from Apache Tamaya!", greeter.greet());
+    }
+
+    public interface Greeter {
+        String greet();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/src/test/resources/META-INF/camelcontext.xml
----------------------------------------------------------------------
diff --git a/camel/src/test/resources/META-INF/camelcontext.xml b/camel/src/test/resources/META-INF/camelcontext.xml
new file mode 100644
index 0000000..6b99d3d
--- /dev/null
+++ b/camel/src/test/resources/META-INF/camelcontext.xml
@@ -0,0 +1,52 @@
+<!--
+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 current 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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+    <!-- this is an included XML file where we only the the routeContext -->
+    <routeContext id="myCoolRoutes" xmlns="http://camel.apache.org/schema/spring">
+        <route id="r1">
+            <from uri="direct:hello1"/>
+            <transform>
+                <simple>{{message}}</simple>
+            </transform>
+        </route>
+        <route id="r2">
+            <from uri="direct:hello2"/>
+            <transform>
+                <simple>{{cfg:message}}</simple>
+            </transform>
+        </route>
+        <route id="r3">
+            <from uri="direct:hello3"/>
+            <transform>
+                <simple>{{tamaya:message}}</simple>
+            </transform>
+        </route>
+    </routeContext>
+
+    <bean id="properties" class="org.apache.tamaya.integration.camel.TamayaPropertiesComponent">
+        <property name="tamayaOverrides" value="true"/>
+    </bean>
+
+</beans>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/src/test/resources/META-INF/javaconfiguration.properties
----------------------------------------------------------------------
diff --git a/camel/src/test/resources/META-INF/javaconfiguration.properties b/camel/src/test/resources/META-INF/javaconfiguration.properties
new file mode 100644
index 0000000..fbe9178
--- /dev/null
+++ b/camel/src/test/resources/META-INF/javaconfiguration.properties
@@ -0,0 +1,19 @@
+#
+# 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 current 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.
+#
+message=Good Bye from Apache Tamaya!
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/camel/src/test/resources/META-INF/routes.xml
----------------------------------------------------------------------
diff --git a/camel/src/test/resources/META-INF/routes.xml b/camel/src/test/resources/META-INF/routes.xml
new file mode 100644
index 0000000..5ec3529
--- /dev/null
+++ b/camel/src/test/resources/META-INF/routes.xml
@@ -0,0 +1,39 @@
+<!--
+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 current 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.
+-->
+<routes xmlns="http://camel.apache.org/schema/spring">
+    <description>Routes for testing.</description>
+    <route>
+        <from uri="direct:hello1"/>
+        <transform>
+            <simple>{{message}}</simple>
+        </transform>
+    </route>
+    <route>
+        <from uri="direct:hello2"/>
+        <transform>
+            <simple>{{cfg:message}}</simple>
+        </transform>
+    </route>
+    <route>
+        <from uri="direct:hello3"/>
+        <transform>
+            <simple>{{tamaya:message}}</simple>
+        </transform>
+    </route>
+</routes>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/consul/pom.xml
----------------------------------------------------------------------
diff --git a/consul/pom.xml b/consul/pom.xml
new file mode 100644
index 0000000..f3d8611
--- /dev/null
+++ b/consul/pom.xml
@@ -0,0 +1,99 @@
+<!-- 
+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 current 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.tamaya.ext</groupId>
+        <artifactId>tamaya-sandbox</artifactId>
+        <version>0.3-incubating-SNAPSHOT</version>
+        <relativePath>..</relativePath>
+    </parent>
+
+    <artifactId>tamaya-consul</artifactId>
+    <name>Apache Tamaya Modules - Consul</name>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.tamaya.consul
+                        </Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>java-hamcrest</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.tamaya</groupId>
+            <artifactId>tamaya-core</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya</groupId>
+            <artifactId>tamaya-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya.ext</groupId>
+            <artifactId>tamaya-functions</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya.ext</groupId>
+            <artifactId>tamaya-mutable-config</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.orbitz.consul</groupId>
+            <artifactId>consul-client</artifactId>
+            <version>0.9.16</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-rs-client</artifactId>
+            <version>3.0.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http-hc</artifactId>
+            <version>3.0.3</version>
+        </dependency>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/consul/src/main/java/org/apache/tamaya/consul/ConsulBackends.java
----------------------------------------------------------------------
diff --git a/consul/src/main/java/org/apache/tamaya/consul/ConsulBackends.java b/consul/src/main/java/org/apache/tamaya/consul/ConsulBackends.java
new file mode 100644
index 0000000..4eab141
--- /dev/null
+++ b/consul/src/main/java/org/apache/tamaya/consul/ConsulBackends.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.tamaya.consul;
+
+import com.google.common.net.HostAndPort;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Singleton that reads and stores the current consul setup, especially the possible host:ports to be used.
+ */
+public final class ConsulBackends {
+
+    private static final Logger LOG = Logger.getLogger(ConsulBackends.class.getName());
+    private static List<HostAndPort> consulBackends = new ArrayList<>();
+
+    static{
+        String serverURLs = System.getProperty("tamaya.consul.urls");
+        if(serverURLs==null){
+            serverURLs = System.getenv("tamaya.consul.urls");
+        }
+        if(serverURLs==null){
+            serverURLs = "127.0.0.1:8300";
+        }
+        for(String url:serverURLs.split("\\,")) {
+            try{
+                consulBackends.add(HostAndPort.fromString(url.trim()));
+                LOG.info("Using consul endoint: " + url);
+            } catch(Exception e){
+                LOG.log(Level.SEVERE, "Error initializing consul accessor for URL: " + url, e);
+            }
+        }
+    }
+
+    private ConsulBackends(){}
+
+    public static List<HostAndPort> getConsulBackends(){
+        return consulBackends;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java
----------------------------------------------------------------------
diff --git a/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java b/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java
new file mode 100644
index 0000000..1936362
--- /dev/null
+++ b/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java
@@ -0,0 +1,198 @@
+/*
+ * 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.tamaya.consul;
+
+import com.google.common.base.Optional;
+import com.google.common.net.HostAndPort;
+import com.orbitz.consul.Consul;
+import com.orbitz.consul.KeyValueClient;
+import com.orbitz.consul.model.kv.Value;
+import org.apache.tamaya.mutableconfig.spi.ConfigChangeRequest;
+import org.apache.tamaya.mutableconfig.spi.MutablePropertySource;
+import org.apache.tamaya.spi.PropertyValue;
+import org.apache.tamaya.spi.PropertyValueBuilder;
+import org.apache.tamaya.spisupport.BasePropertySource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Propertysource that is reading configuration from a configured consul endpoint. Setting
+ * {@code consul.prefix} as system property maps the consul based onfiguration
+ * to this prefix namespace. Consul servers are configured as {@code consul.urls} system or environment property.
+ */
+public class ConsulPropertySource extends BasePropertySource
+implements MutablePropertySource{
+    private static final Logger LOG = Logger.getLogger(ConsulPropertySource.class.getName());
+
+    private String prefix = System.getProperty("tamaya.consul.prefix", "");
+
+
+    @Override
+    public int getOrdinal() {
+        PropertyValue configuredOrdinal = get(TAMAYA_ORDINAL);
+        if(configuredOrdinal!=null){
+            try{
+                return Integer.parseInt(configuredOrdinal.getValue());
+            } catch(Exception e){
+                Logger.getLogger(getClass().getName()).log(Level.WARNING,
+                        "Configured Ordinal is not an int number: " + configuredOrdinal, e);
+            }
+        }
+        return getDefaultOrdinal();
+    }
+
+    /**
+     * Returns the  default ordinal used, when no ordinal is set, or the ordinal was not parseable to an int value.
+     * @return the  default ordinal used, by default 1000.
+     */
+    public int getDefaultOrdinal(){
+        return 1000;
+    }
+
+    @Override
+    public String getName() {
+        return "consul";
+    }
+
+    @Override
+    public PropertyValue get(String key) {
+        // check prefix, if key does not start with it, it is not part of our name space
+        // if so, the prefix part must be removedProperties, so etcd can resolve without it
+        if(!key.startsWith(prefix)){
+            return null;
+        } else{
+            key = key.substring(prefix.length());
+        }
+        String reqKey = key;
+        if(key.startsWith("_")){
+            reqKey = key.substring(1);
+            if(reqKey.endsWith(".createdIndex")){
+                reqKey = reqKey.substring(0,reqKey.length()-".createdIndex".length());
+            } else if(reqKey.endsWith(".modifiedIndex")){
+                reqKey = reqKey.substring(0,reqKey.length()-".modifiedIndex".length());
+            } else if(reqKey.endsWith(".ttl")){
+                reqKey = reqKey.substring(0,reqKey.length()-".ttl".length());
+            } else if(reqKey.endsWith(".expiration")){
+                reqKey = reqKey.substring(0,reqKey.length()-".expiration".length());
+            } else if(reqKey.endsWith(".source")){
+                reqKey = reqKey.substring(0,reqKey.length()-".source".length());
+            }
+        }
+        for(HostAndPort hostAndPort: ConsulBackends.getConsulBackends()){
+            try{
+                Consul consul = Consul.builder().withHostAndPort(hostAndPort).build();
+                KeyValueClient kvClient = consul.keyValueClient();
+                Optional<Value> valueOpt = kvClient.getValue(reqKey);
+                if(!valueOpt.isPresent()) {
+                    LOG.log(Level.FINE, "key not found in consul: " + reqKey);
+                }else{
+                    // No repfix mapping necessary here, since we only access/return the value...
+                    Value value = valueOpt.get();
+                    Map<String,String> props = new HashMap<>();
+                    props.put(reqKey+".createIndex", String.valueOf(value.getCreateIndex()));
+                    props.put(reqKey+".modifyIndex", String.valueOf(value.getModifyIndex()));
+                    props.put(reqKey+".lockIndex", String.valueOf(value.getLockIndex()));
+                    props.put(reqKey+".flags", String.valueOf(value.getFlags()));
+                    return new PropertyValueBuilder(key, value.getValue().get(), getName()).setContextData(props).build();
+                }
+            } catch(Exception e){
+                LOG.log(Level.FINE, "etcd access failed on " + hostAndPort + ", trying next...", e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+//        for(HostAndPort hostAndPort: ConsulBackends.getConsulBackends()){
+//            try{
+//                Consul consul = Consul.builder().withHostAndPort(hostAndPort).build();
+//                KeyValueClient kvClient = consul.keyValueClient();
+//                Optional<Value> valueOpt = kvClient.getValue(reqKey);
+//                try{
+//                    Map<String, String> props = kvClient.getProperties("");
+//                    if(!props.containsKey("_ERROR")) {
+//                        return mapPrefix(props);
+//                    } else{
+//                        LOG.log(Level.FINE, "consul error on " + hostAndPort + ": " + props.get("_ERROR"));
+//                    }
+//                } catch(Exception e){
+//                    LOG.log(Level.FINE, "consul access failed on " + hostAndPort + ", trying next...", e);
+//                }
+//            } catch(Exception e){
+//                LOG.log(Level.FINE, "etcd access failed on " + hostAndPort + ", trying next...", e);
+//            }
+//        }
+        return Collections.emptyMap();
+    }
+
+    private Map<String, String> mapPrefix(Map<String, String> props) {
+        if(prefix.isEmpty()){
+            return props;
+        }
+        Map<String,String> map = new HashMap<>();
+        for(Map.Entry<String,String> entry:props.entrySet()){
+            if(entry.getKey().startsWith("_")){
+                map.put("_" + prefix + entry.getKey().substring(1), entry.getValue());
+            } else{
+                map.put(prefix+ entry.getKey(), entry.getValue());
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public boolean isScannable() {
+        return false;
+    }
+
+    @Override
+    public void applyChange(ConfigChangeRequest configChange) {
+        for(HostAndPort hostAndPort: ConsulBackends.getConsulBackends()){
+            try{
+                Consul consul = Consul.builder().withHostAndPort(hostAndPort).build();
+                KeyValueClient kvClient = consul.keyValueClient();
+
+                for(String k: configChange.getRemovedProperties()){
+                    try{
+                        kvClient.deleteKey(k);
+                    } catch(Exception e){
+                        LOG.info("Failed to remove key from consul: " + k);
+                    }
+                }
+                for(Map.Entry<String,String> en:configChange.getAddedProperties().entrySet()){
+                    String key = en.getKey();
+                    try{
+                        kvClient.putValue(key,en.getValue());
+                    }catch(Exception e) {
+                        LOG.info("Failed to add key to consul: " + en.getKey() + "=" + en.getValue());
+                    }
+                }
+                // success: stop here
+                break;
+            } catch(Exception e){
+                LOG.log(Level.FINE, "consul access failed on " + hostAndPort + ", trying next...", e);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/consul/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource
----------------------------------------------------------------------
diff --git a/consul/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource b/consul/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource
new file mode 100644
index 0000000..4996059
--- /dev/null
+++ b/consul/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource
@@ -0,0 +1,19 @@
+#
+# 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 current 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.
+#
+org.apache.tamaya.consul.ConsulPropertySource
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/etcd/pom.xml
----------------------------------------------------------------------
diff --git a/etcd/pom.xml b/etcd/pom.xml
new file mode 100644
index 0000000..c687b6a
--- /dev/null
+++ b/etcd/pom.xml
@@ -0,0 +1,97 @@
+<!-- 
+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 current 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.tamaya.ext</groupId>
+        <artifactId>tamaya-sandbox</artifactId>
+        <version>0.3-incubating-SNAPSHOT</version>
+        <relativePath>..</relativePath>
+    </parent>
+
+    <artifactId>tamaya-etcd</artifactId>
+    <name>Apache Tamaya Modules - etcd PropertySource</name>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.tamaya.etcd
+                        </Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>java-hamcrest</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya</groupId>
+            <artifactId>tamaya-core</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya</groupId>
+            <artifactId>tamaya-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya.ext</groupId>
+            <artifactId>tamaya-functions</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient-osgi</artifactId>
+            <version>4.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-json_1.0_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tamaya.ext</groupId>
+            <artifactId>tamaya-mutable-config</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java b/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
new file mode 100644
index 0000000..4feccfa
--- /dev/null
+++ b/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java
@@ -0,0 +1,520 @@
+/*
+ * 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.tamaya.etcd;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * Accessor for reading to or writing from an etcd endpoint.
+ */
+public class EtcdAccessor {
+
+    private static final Logger LOG = Logger.getLogger(EtcdAccessor.class.getName());
+
+    /**
+     * Timeout in seconds.
+     */
+    private int timeout = 2;
+    /**
+     * Timeout in seconds.
+     */
+    private final int socketTimeout = 1000;
+    /**
+     * Timeout in seconds.
+     */
+    private final int connectTimeout = 1000;
+
+    /**
+     * Property that make Johnzon accept commentc.
+     */
+    public static final String JOHNZON_SUPPORTS_COMMENTS_PROP = "org.apache.johnzon.supports-comments";
+    /**
+     * The JSON reader factory used.
+     */
+    private final JsonReaderFactory readerFactory = initReaderFactory();
+
+    /**
+     * Initializes the factory to be used for creating readers.
+     */
+    private JsonReaderFactory initReaderFactory() {
+        final Map<String, Object> config = new HashMap<>();
+        config.put(JOHNZON_SUPPORTS_COMMENTS_PROP, true);
+        return Json.createReaderFactory(config);
+    }
+
+    /**
+     * The base server url.
+     */
+    private final String serverURL;
+    /**
+     * The http client.
+     */
+    private final CloseableHttpClient httpclient = HttpClients.createDefault();
+
+    /**
+     * Creates a new instance with the basic access url.
+     *
+     * @param server server url, e.g. {@code http://127.0.0.1:4001}, not null.
+     */
+    public EtcdAccessor(String server) {
+        this(server, 2);
+    }
+
+    public EtcdAccessor(String server, int timeout) {
+        this.timeout = timeout;
+        if (server.endsWith("/")) {
+            serverURL = server.substring(0, server.length() - 1);
+        } else {
+            serverURL = server;
+        }
+
+    }
+
+    /**
+     * Get the etcd server version.
+     *
+     * @return the etcd server version, never null.
+     */
+    public String getVersion() {
+        String version = "<ERROR>";
+        try {
+            final CloseableHttpClient httpclient = HttpClients.createDefault();
+            final HttpGet httpGet = new HttpGet(serverURL + "/version");
+            httpGet.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout)
+                    .setConnectTimeout(timeout).build());
+            try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
+                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                    final HttpEntity entity = response.getEntity();
+                    // and ensure it is fully consumed
+                    version = EntityUtils.toString(entity);
+                    EntityUtils.consume(entity);
+                }
+            }
+            return version;
+        } catch (final Exception e) {
+            LOG.log(Level.INFO, "Error getting etcd version from: " + serverURL, e);
+        }
+        return version;
+    }
+
+    /**
+     * Ask etcd for a single key, value pair. Hereby the response returned from
+     * etcd:
+     * 
+     * <pre>
+     * {
+     * "action": "get",
+     * "node": {
+     * "createdIndex": 2,
+     * "key": "/message",
+     * "modifiedIndex": 2,
+     * "value": "Hello world"
+     * }
+     * }
+     * </pre>
+     * 
+     * is mapped to:
+     * 
+     * <pre>
+     *     key=value
+     *     _key.source=[etcd]http://127.0.0.1:4001
+     *     _key.createdIndex=12
+     *     _key.modifiedIndex=34
+     *     _key.ttl=300
+     *     _key.expiration=...
+     * </pre>
+     *
+     * @param key the requested key
+     * @return the mapped result, including meta-entries.
+     */
+    public Map<String, String> get(String key) {
+        final Map<String, String> result = new HashMap<>();
+        try {
+            final HttpGet httpGet = new HttpGet(serverURL + "/v2/keys/" + key);
+            httpGet.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout)
+                    .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build());
+            try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
+                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                    final HttpEntity entity = response.getEntity();
+                    final JsonReader reader = readerFactory
+                            .createReader(new StringReader(EntityUtils.toString(entity)));
+                    final JsonObject o = reader.readObject();
+                    final JsonObject node = o.getJsonObject("node");
+                    if (node.containsKey("value")) {
+                        result.put(key, node.getString("value"));
+                        result.put("_" + key + ".source", "[etcd]" + serverURL);
+                    }
+                    if (node.containsKey("createdIndex")) {
+                        result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex")));
+                    }
+                    if (node.containsKey("modifiedIndex")) {
+                        result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
+                    }
+                    if (node.containsKey("expiration")) {
+                        result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration")));
+                    }
+                    if (node.containsKey("ttl")) {
+                        result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl")));
+                    }
+                    EntityUtils.consume(entity);
+                } else {
+                    result.put("_" + key + ".NOT_FOUND.target", "[etcd]" + serverURL);
+                }
+            }
+        } catch (final Exception e) {
+            LOG.log(Level.INFO, "Error reading key '" + key + "' from etcd: " + serverURL, e);
+            result.put("_ERROR", "Error reading key '" + key + "' from etcd: " + serverURL + ": " + e.toString());
+        }
+        return result;
+    }
+
+    /**
+     * Creates/updates an entry in etcd without any ttl set.
+     *
+     * @param key   the property key, not null
+     * @param value the value to be set
+     * @return the result map as described above.
+     * @see #set(String, String, Integer)
+     */
+    public Map<String, String> set(String key, String value) {
+        return set(key, value, null);
+    }
+
+    /**
+     * Creates/updates an entry in etcd. The response as follows:
+     * 
+     * <pre>
+     *     {
+     * "action": "set",
+     * "node": {
+     * "createdIndex": 3,
+     * "key": "/message",
+     * "modifiedIndex": 3,
+     * "value": "Hello etcd"
+     * },
+     * "prevNode": {
+     * "createdIndex": 2,
+     * "key": "/message",
+     * "value": "Hello world",
+     * "modifiedIndex": 2
+     * }
+     * }
+     * </pre>
+     * 
+     * is mapped to:
+     * 
+     * <pre>
+     *     key=value
+     *     _key.source=[etcd]http://127.0.0.1:4001
+     *     _key.createdIndex=12
+     *     _key.modifiedIndex=34
+     *     _key.ttl=300
+     *     _key.expiry=...
+     *      // optional
+     *     _key.prevNode.createdIndex=12
+     *     _key.prevNode.modifiedIndex=34
+     *     _key.prevNode.ttl=300
+     *     _key.prevNode.expiration=...
+     * </pre>
+     *
+     * @param key        the property key, not null
+     * @param value      the value to be set
+     * @param ttlSeconds the ttl in seconds (optional)
+     * @return the result map as described above.
+     */
+    public Map<String, String> set(String key, String value, Integer ttlSeconds) {
+        final Map<String, String> result = new HashMap<>();
+        try {
+            final HttpPut put = new HttpPut(serverURL + "/v2/keys/" + key);
+            put.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout)
+                    .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build());
+            final List<NameValuePair> nvps = new ArrayList<>();
+            nvps.add(new BasicNameValuePair("value", value));
+            if (ttlSeconds != null) {
+                nvps.add(new BasicNameValuePair("ttl", ttlSeconds.toString()));
+            }
+            put.setEntity(new UrlEncodedFormEntity(nvps));
+            try (CloseableHttpResponse response = httpclient.execute(put)) {
+                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED
+                        || response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                    final HttpEntity entity = response.getEntity();
+                    final JsonReader reader = readerFactory
+                            .createReader(new StringReader(EntityUtils.toString(entity)));
+                    final JsonObject o = reader.readObject();
+                    final JsonObject node = o.getJsonObject("node");
+                    if (node.containsKey("createdIndex")) {
+                        result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex")));
+                    }
+                    if (node.containsKey("modifiedIndex")) {
+                        result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
+                    }
+                    if (node.containsKey("expiration")) {
+                        result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration")));
+                    }
+                    if (node.containsKey("ttl")) {
+                        result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl")));
+                    }
+                    result.put(key, node.getString("value"));
+                    result.put("_" + key + ".source", "[etcd]" + serverURL);
+                    parsePrevNode(key, result, node);
+                    EntityUtils.consume(entity);
+                }
+            }
+        } catch (final Exception e) {
+            LOG.log(Level.INFO, "Error writing to etcd: " + serverURL, e);
+            result.put("_ERROR", "Error writing '" + key + "' to etcd: " + serverURL + ": " + e.toString());
+        }
+        return result;
+    }
+
+    /**
+     * Deletes a given key. The response is as follows:
+     * 
+     * <pre>
+     *     _key.source=[etcd]http://127.0.0.1:4001
+     *     _key.createdIndex=12
+     *     _key.modifiedIndex=34
+     *     _key.ttl=300
+     *     _key.expiry=...
+     *      // optional
+     *     _key.prevNode.createdIndex=12
+     *     _key.prevNode.modifiedIndex=34
+     *     _key.prevNode.ttl=300
+     *     _key.prevNode.expiration=...
+     *     _key.prevNode.value=...
+     * </pre>
+     *
+     * @param key the key to be deleted.
+     * @return the response mpas as described above.
+     */
+    public Map<String, String> delete(String key) {
+        final Map<String, String> result = new HashMap<>();
+        try {
+            final HttpDelete delete = new HttpDelete(serverURL + "/v2/keys/" + key);
+            delete.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout)
+                    .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build());
+            try (CloseableHttpResponse response = httpclient.execute(delete)) {
+                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                    final HttpEntity entity = response.getEntity();
+                    final JsonReader reader = readerFactory
+                            .createReader(new StringReader(EntityUtils.toString(entity)));
+                    final JsonObject o = reader.readObject();
+                    final JsonObject node = o.getJsonObject("node");
+                    if (node.containsKey("createdIndex")) {
+                        result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex")));
+                    }
+                    if (node.containsKey("modifiedIndex")) {
+                        result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
+                    }
+                    if (node.containsKey("expiration")) {
+                        result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration")));
+                    }
+                    if (node.containsKey("ttl")) {
+                        result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl")));
+                    }
+                    parsePrevNode(key, result, o);
+                    EntityUtils.consume(entity);
+                }
+            }
+        } catch (final Exception e) {
+            LOG.log(Level.INFO, "Error deleting key '" + key + "' from etcd: " + serverURL, e);
+            result.put("_ERROR", "Error deleting '" + key + "' from etcd: " + serverURL + ": " + e.toString());
+        }
+        return result;
+    }
+
+    private static void parsePrevNode(String key, Map<String, String> result, JsonObject o) {
+        if (o.containsKey("prevNode")) {
+            final JsonObject prevNode = o.getJsonObject("prevNode");
+            if (prevNode.containsKey("createdIndex")) {
+                result.put("_" + key + ".prevNode.createdIndex",
+                        String.valueOf(prevNode.getInt("createdIndex")));
+            }
+            if (prevNode.containsKey("modifiedIndex")) {
+                result.put("_" + key + ".prevNode.modifiedIndex",
+                        String.valueOf(prevNode.getInt("modifiedIndex")));
+            }
+            if (prevNode.containsKey("expiration")) {
+                result.put("_" + key + ".prevNode.expiration",
+                        String.valueOf(prevNode.getString("expiration")));
+            }
+            if (prevNode.containsKey("ttl")) {
+                result.put("_" + key + ".prevNode.ttl", String.valueOf(prevNode.getInt("ttl")));
+            }
+            result.put("_" + key + ".prevNode.value", prevNode.getString("value"));
+        }
+    }
+
+    /**
+     * Get all properties for the given directory key recursively.
+     *
+     * @param directory the directory entry
+     * @return the properties and its metadata
+     * @see #getProperties(String, boolean)
+     */
+    public Map<String, String> getProperties(String directory) {
+        return getProperties(directory, true);
+    }
+
+    /**
+     * Access all properties. The response of:
+     * 
+     * <pre>
+     * {
+     * "action": "get",
+     * "node": {
+     * "key": "/",
+     * "dir": true,
+     * "nodes": [
+     * {
+     * "key": "/foo_dir",
+     * "dir": true,
+     * "modifiedIndex": 2,
+     * "createdIndex": 2
+     * },
+     * {
+     * "key": "/foo",
+     * "value": "two",
+     * "modifiedIndex": 1,
+     * "createdIndex": 1
+     * }
+     * ]
+     * }
+     * }
+     * </pre>
+     * 
+     * is mapped to a regular Tamaya properties map as follows:
+     * 
+     * <pre>
+     *    key1=myvalue
+     *     _key1.source=[etcd]http://127.0.0.1:4001
+     *     _key1.createdIndex=12
+     *     _key1.modifiedIndex=34
+     *     _key1.ttl=300
+     *     _key1.expiration=...
+     *
+     *      key2=myvaluexxx
+     *     _key2.source=[etcd]http://127.0.0.1:4001
+     *     _key2.createdIndex=12
+     *
+     *      key3=val3
+     *     _key3.source=[etcd]http://127.0.0.1:4001
+     *     _key3.createdIndex=12
+     *     _key3.modifiedIndex=2
+     * </pre>
+     *
+     * @param directory remote directory to query.
+     * @param recursive allows to set if querying is performed recursively
+     * @return all properties read from the remote server.
+     */
+    public Map<String, String> getProperties(String directory, boolean recursive) {
+        final Map<String, String> result = new HashMap<>();
+        try {
+            final HttpGet get = new HttpGet(serverURL + "/v2/keys/" + directory + "?recursive=" + recursive);
+            get.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setSocketTimeout(socketTimeout)
+                    .setConnectionRequestTimeout(timeout).setConnectTimeout(connectTimeout).build());
+            try (CloseableHttpResponse response = httpclient.execute(get)) {
+
+                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                    final HttpEntity entity = response.getEntity();
+                    final JsonReader reader = readerFactory.createReader(new StringReader(EntityUtils.toString(entity)));
+                    final JsonObject o = reader.readObject();
+                    final JsonObject node = o.getJsonObject("node");
+                    if (node != null) {
+                        addNodes(result, node);
+                    }
+                    EntityUtils.consume(entity);
+                }
+            }
+        } catch (final Exception e) {
+            LOG.log(Level.INFO, "Error reading properties for '" + directory + "' from etcd: " + serverURL, e);
+            result.put("_ERROR",
+                    "Error reading properties for '" + directory + "' from etcd: " + serverURL + ": " + e.toString());
+        }
+        return result;
+    }
+
+    /**
+     * Recursively read out all key/values from this etcd JSON array.
+     *
+     * @param result map with key, values and metadata.
+     * @param node   the node to parse.
+     */
+    private void addNodes(Map<String, String> result, JsonObject node) {
+        if (!node.containsKey("dir") || "false".equals(node.get("dir").toString())) {
+            final String key = node.getString("key").substring(1);
+            result.put(key, node.getString("value"));
+            if (node.containsKey("createdIndex")) {
+                result.put("_" + key + ".createdIndex", String.valueOf(node.getInt("createdIndex")));
+            }
+            if (node.containsKey("modifiedIndex")) {
+                result.put("_" + key + ".modifiedIndex", String.valueOf(node.getInt("modifiedIndex")));
+            }
+            if (node.containsKey("expiration")) {
+                result.put("_" + key + ".expiration", String.valueOf(node.getString("expiration")));
+            }
+            if (node.containsKey("ttl")) {
+                result.put("_" + key + ".ttl", String.valueOf(node.getInt("ttl")));
+            }
+            result.put("_" + key + ".source", "[etcd]" + serverURL);
+        } else {
+            final JsonArray nodes = node.getJsonArray("nodes");
+            if (nodes != null) {
+                for (int i = 0; i < nodes.size(); i++) {
+                    addNodes(result, nodes.getJsonObject(i));
+                }
+            }
+        }
+    }
+
+    /**
+     * Access the server root URL used by this accessor.
+     *
+     * @return the server root URL.
+     */
+    public String getUrl() {
+        return serverURL;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackends.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackends.java b/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackends.java
new file mode 100644
index 0000000..a0c0703
--- /dev/null
+++ b/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackends.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tamaya.etcd;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Singleton that reads and stores the current etcd setup, especially the possible URLs to be used.
+ */
+public final class EtcdBackends {
+
+    private static final Logger LOG = Logger.getLogger(EtcdBackends.class.getName());
+    private static List<EtcdAccessor> etcdBackends = new ArrayList<>();
+
+    static{
+        int timeout = 2;
+        String val = System.getProperty("tamaya.etcd.timeout");
+        if(val == null){
+            val = System.getenv("tamaya.etcd.timeout");
+        }
+        if(val!=null){
+            timeout = Integer.parseInt(val);
+        }
+        String serverURLs = System.getProperty("tamaya.etcd.server.urls");
+        if(serverURLs==null){
+            serverURLs = System.getenv("tamaya.etcd.server.urls");
+        }
+        if(serverURLs==null){
+            serverURLs = "http://127.0.0.1:4001";
+        }
+        for(String url:serverURLs.split("\\,")) {
+            try{
+                etcdBackends.add(new EtcdAccessor(url.trim(), timeout));
+                LOG.info("Using etcd endoint: " + url);
+            } catch(Exception e){
+                LOG.log(Level.SEVERE, "Error initializing etcd accessor for URL: " + url, e);
+            }
+        }
+    }
+
+    private EtcdBackends(){}
+
+    public static List<EtcdAccessor> getEtcdBackends(){
+        return etcdBackends;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
----------------------------------------------------------------------
diff --git a/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java b/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
new file mode 100644
index 0000000..5e129f7
--- /dev/null
+++ b/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java
@@ -0,0 +1,209 @@
+/*
+ * 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.tamaya.etcd;
+
+import org.apache.tamaya.mutableconfig.spi.ConfigChangeRequest;
+import org.apache.tamaya.mutableconfig.spi.MutablePropertySource;
+import org.apache.tamaya.spi.PropertyValue;
+import org.apache.tamaya.spi.PropertyValueBuilder;
+import org.apache.tamaya.spisupport.BasePropertySource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Propertysource that is reading configuration from a configured etcd endpoint. Setting
+ * {@code etcd.prefix} as system property maps the etcd based onfiguration
+ * to this prefix namespace. Etcd servers are configured as {@code etcd.server.urls} system or environment property.
+ * ETcd can be disabled by setting {@code tamaya.etcdprops.disable} either as env or system property.
+ */
+public class EtcdPropertySource extends BasePropertySource
+        implements MutablePropertySource{
+    private static final Logger LOG = Logger.getLogger(EtcdPropertySource.class.getName());
+
+    private String prefix = System.getProperty("tamaya.etcd.prefix", "");
+
+    private final boolean disabled = evaluateDisabled();
+
+    private boolean evaluateDisabled() {
+        String value = System.getProperty("tamaya.etcdprops.disable");
+        if(value==null){
+            value = System.getenv("tamaya.etcdprops.disable");
+        }
+        if(value==null){
+            return false;
+        }
+        return value.isEmpty() || Boolean.parseBoolean(value);
+    }
+
+    @Override
+    public int getOrdinal() {
+        PropertyValue configuredOrdinal = get(TAMAYA_ORDINAL);
+        if(configuredOrdinal!=null){
+            try{
+                return Integer.parseInt(configuredOrdinal.getValue());
+            } catch(Exception e){
+                Logger.getLogger(getClass().getName()).log(Level.WARNING,
+                        "Configured Ordinal is not an int number: " + configuredOrdinal, e);
+            }
+        }
+        return getDefaultOrdinal();
+    }
+
+    /**
+     * Returns the  default ordinal used, when no ordinal is set, or the ordinal was not parseable to an int value.
+     * @return the  default ordinal used, by default 0.
+     */
+    public int getDefaultOrdinal(){
+        return 1000;
+    }
+
+    @Override
+    public String getName() {
+        return "etcd";
+    }
+
+    @Override
+    public PropertyValue get(String key) {
+        if(disabled){
+            return null;
+        }
+        // check prefix, if key does not start with it, it is not part of our name space
+        // if so, the prefix part must be removedProperties, so etcd can resolve without it
+        if(!key.startsWith(prefix)){
+            return null;
+        } else{
+            key = key.substring(prefix.length());
+        }
+        Map<String,String> props;
+        String reqKey = key;
+        if(key.startsWith("_")){
+            reqKey = key.substring(1);
+            if(reqKey.endsWith(".createdIndex")){
+                reqKey = reqKey.substring(0,reqKey.length()-".createdIndex".length());
+            } else if(reqKey.endsWith(".modifiedIndex")){
+                reqKey = reqKey.substring(0,reqKey.length()-".modifiedIndex".length());
+            } else if(reqKey.endsWith(".ttl")){
+                reqKey = reqKey.substring(0,reqKey.length()-".ttl".length());
+            } else if(reqKey.endsWith(".expiration")){
+                reqKey = reqKey.substring(0,reqKey.length()-".expiration".length());
+            } else if(reqKey.endsWith(".source")){
+                reqKey = reqKey.substring(0,reqKey.length()-".source".length());
+            }
+        }
+        for(EtcdAccessor accessor: EtcdBackends.getEtcdBackends()){
+            try{
+                props = accessor.get(reqKey);
+                if(!props.containsKey("_ERROR")) {
+                    // No repfix mapping necessary here, since we only access/return the value...
+                    return new PropertyValueBuilder(key, props.get(reqKey), getName()).setContextData(props).build();
+                } else{
+                    LOG.log(Level.FINE, "etcd error on " + accessor.getUrl() + ": " + props.get("_ERROR"));
+                }
+            } catch(Exception e){
+                LOG.log(Level.FINE, "etcd access failed on " + accessor.getUrl() + ", trying next...", e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        if(disabled){
+            return Collections.emptyMap();
+        }
+        if(!EtcdBackends.getEtcdBackends().isEmpty()){
+            for(EtcdAccessor accessor: EtcdBackends.getEtcdBackends()){
+                try{
+                    Map<String, String> props = accessor.getProperties("");
+                    if(!props.containsKey("_ERROR")) {
+                        return mapPrefix(props);
+                    } else{
+                        LOG.log(Level.FINE, "etcd error on " + accessor.getUrl() + ": " + props.get("_ERROR"));
+                    }
+                } catch(Exception e){
+                    LOG.log(Level.FINE, "etcd access failed on " + accessor.getUrl() + ", trying next...", e);
+                }
+            }
+        }
+        return Collections.emptyMap();
+    }
+
+    private Map<String, String> mapPrefix(Map<String, String> props) {
+        if(prefix.isEmpty()){
+            return props;
+        }
+        Map<String,String> map = new HashMap<>();
+        for(Map.Entry<String,String> entry:props.entrySet()){
+            if(entry.getKey().startsWith("_")){
+                map.put("_" + prefix + entry.getKey().substring(1), entry.getValue());
+            } else{
+                map.put(prefix+ entry.getKey(), entry.getValue());
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public boolean isScannable() {
+        return true;
+    }
+
+    @Override
+    public void applyChange(ConfigChangeRequest configChange) {
+        for(EtcdAccessor accessor: EtcdBackends.getEtcdBackends()){
+            try{
+                for(String k: configChange.getRemovedProperties()){
+                    Map<String,String> res = accessor.delete(k);
+                    if(res.get("_ERROR")!=null){
+                        LOG.info("Failed to remove key from etcd: " + k);
+                    }
+                }
+                for(Map.Entry<String,String> en:configChange.getAddedProperties().entrySet()){
+                    String key = en.getKey();
+                    Integer ttl = null;
+                    int index = en.getKey().indexOf('?');
+                    if(index>0){
+                        key = en.getKey().substring(0, index);
+                        String rawQuery = en.getKey().substring(index+1);
+                        String[] queries = rawQuery.split("&");
+                        for(String query:queries){
+                            if(query.contains("ttl")){
+                                int qIdx = query.indexOf('=');
+                                ttl = qIdx>0?Integer.parseInt(query.substring(qIdx+1).trim()):null;
+                            }
+                        }
+                    }
+                    Map<String,String> res = accessor.set(key, en.getValue(), ttl);
+                    if(res.get("_ERROR")!=null){
+                        LOG.info("Failed to add key to etcd: " + en.getKey()  + "=" + en.getValue());
+                    }
+                }
+                // success, stop here
+                break;
+            } catch(Exception e){
+                LOG.log(Level.FINE, "etcd access failed on " + accessor.getUrl() + ", trying next...", e);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/etcd/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource
----------------------------------------------------------------------
diff --git a/etcd/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource b/etcd/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource
new file mode 100644
index 0000000..eb7958e
--- /dev/null
+++ b/etcd/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertySource
@@ -0,0 +1,19 @@
+#
+# 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 current 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.
+#
+org.apache.tamaya.etcd.EtcdPropertySource
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya-sandbox/blob/358828fe/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java
----------------------------------------------------------------------
diff --git a/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java b/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java
new file mode 100644
index 0000000..80bd716
--- /dev/null
+++ b/etcd/src/test/java/org/apache/tamaya/etcd/EtcdAccessorTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.tamaya.etcd;
+
+import org.junit.BeforeClass;
+
+import java.net.MalformedURLException;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for th etcd backend integration. You must have set a system property so, theses tests are executed, e.g.
+ * {@code -Detcd.url=http://127.0.0.1:4001}.
+ */
+public class EtcdAccessorTest {
+
+    private static EtcdAccessor accessor;
+    static boolean execute = false;
+
+    @BeforeClass
+    public static void setup() throws MalformedURLException {
+        accessor = new EtcdAccessor("http://192.168.99.105:4001");
+        if(!accessor.getVersion().contains("etcd")){
+            System.out.println("Disabling etcd tests, etcd not accessible at: " + System.getProperty("etcd.server.urls"));
+            System.out.println("Configure etcd with -Detcd.server.urls=http://<IP>:<PORT>");
+        }
+        else{
+            execute = true;
+        }
+    }
+
+    @org.junit.Test
+    public void testGetVersion() throws Exception {
+        if(!execute)return;
+        assertEquals(accessor.getVersion(), "etcd 0.4.9");
+    }
+
+    @org.junit.Test
+    public void testGet() throws Exception {
+        if(!execute)return;
+        Map<String,String> result = accessor.get("test1");
+        assertNotNull(result);
+    }
+
+    @org.junit.Test
+    public void testSetNormal() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testSetNormal", value);
+        assertNull(result.get("_testSetNormal.ttl"));
+        assertEquals(accessor.get("testSetNormal").get("testSetNormal"), value);
+    }
+
+    @org.junit.Test
+    public void testSetNormal2() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testSetNormal2", value, null);
+        assertNull(result.get("_testSetNormal2.ttl"));
+        assertEquals(accessor.get("testSetNormal2").get("testSetNormal2"), value);
+    }
+
+    @org.junit.Test
+    public void testSetWithTTL() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testSetWithTTL", value, 1);
+        assertNotNull(result.get("_testSetWithTTL.ttl"));
+        assertEquals(accessor.get("testSetWithTTL").get("testSetWithTTL"), value);
+        Thread.sleep(2000L);
+        result = accessor.get("testSetWithTTL");
+        assertNull(result.get("testSetWithTTL"));
+    }
+
+
+    @org.junit.Test
+    public void testDelete() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        Map<String,String> result = accessor.set("testDelete", value, null);
+        assertEquals(accessor.get("testDelete").get("testDelete"), value);
+        assertNotNull(result.get("_testDelete.createdIndex"));
+        result = accessor.delete("testDelete");
+        assertEquals(result.get("_testDelete.prevNode.value"),value);
+        assertNull(accessor.get("testDelete").get("testDelete"));
+    }
+
+    @org.junit.Test
+    public void testGetProperties() throws Exception {
+        if(!execute)return;
+        String value = UUID.randomUUID().toString();
+        accessor.set("testGetProperties1", value);
+        Map<String,String> result = accessor.getProperties("");
+        assertNotNull(result);
+        assertEquals(result.get("testGetProperties1"), value);
+        assertNotNull(result.get("_testGetProperties1.createdIndex"));
+    }
+}
\ No newline at end of file


Mime
View raw message