You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2016/11/01 23:47:26 UTC
[4/5] incubator-tamaya-sandbox git commit: Added missing files,
synched Workspace.
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 <an...@apache.org>
Authored: Wed Nov 2 00:46:53 2016 +0100
Committer: anatole <an...@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