http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/collections/src/test/java/org/apache/tamaya/collections/CollectionsTypedTests.java ---------------------------------------------------------------------- diff --git a/modules/collections/src/test/java/org/apache/tamaya/collections/CollectionsTypedTests.java b/modules/collections/src/test/java/org/apache/tamaya/collections/CollectionsTypedTests.java new file mode 100644 index 0000000..92c592e --- /dev/null +++ b/modules/collections/src/test/java/org/apache/tamaya/collections/CollectionsTypedTests.java @@ -0,0 +1,207 @@ +/* + * 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.collections; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.TypeLiteral; +import org.junit.Test; + +import java.util.*; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Basic tests for Tamaya collection support. Relevant configs for this tests: + * <pre>base.items=1,2,3,4,5,6,7,8,9,0 + * base.map=1::a, 2::b, 3::c, [4:: ] + * </pre> + */ +public class CollectionsTypedTests { + + @Test + public void testArrayListList_String(){ + Configuration config = Configuration.current(); + List<String> items = config.get("typed2.arraylist", new TypeLiteral<List<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof ArrayList); + items = (List<String>) config.get("typed2.arraylist", List.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof ArrayList); + } + + @Test + public void testLinkedListList_String(){ + Configuration config = Configuration.current(); + List<String> items = config.get("typed2.linkedlist", new TypeLiteral<List<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof LinkedList); + items = (List<String>) config.get("typed2.linkedlist", List.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof LinkedList); + } + + + @Test + public void testHashSet_String(){ + Configuration config = Configuration.current(); + Set<String> items = config.get("typed2.hashset", new TypeLiteral<Set<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof HashSet); + items = (Set<String>) config.get("typed2.hashset", Set.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof HashSet); + } + + @Test + public void testTreeSet_String(){ + Configuration config = Configuration.current(); + Set<String> items = config.get("typed2.treeset", new TypeLiteral<Set<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof TreeSet); + items = (Set<String>) config.get("typed2.treeset", Set.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof TreeSet); + } + + @Test + public void testHashMap_String(){ + Configuration config = Configuration.current(); + Map<String,String> items = config.get("typed2.hashmap", new TypeLiteral<Map<String,String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(4, items.size()); + assertEquals("a", items.get("1")); + assertEquals("b", items.get("2")); + assertEquals("c", items.get("3")); + assertEquals(" ", items.get("4")); + assertTrue(items instanceof HashMap); + items = (Map<String,String>) config.get("typed2.hashmap", Map.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(4, items.size()); + assertEquals("a", items.get("1")); + assertEquals("b", items.get("2")); + assertEquals("c", items.get("3")); + assertEquals(" ", items.get("4")); + assertTrue(items instanceof HashMap); + } + + @Test + public void testTreeMap_String(){ + Configuration config = Configuration.current(); + Map<String,String> items = config.get("typed2.treemap", new TypeLiteral<Map<String,String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(4, items.size()); + assertEquals("a", items.get("1")); + assertEquals("b", items.get("2")); + assertEquals("c", items.get("3")); + assertEquals(" ", items.get("4")); + assertTrue(items instanceof TreeMap); + items = (Map<String,String>) config.get("typed2.treemap", Map.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(4, items.size()); + assertEquals("a", items.get("1")); + assertEquals("b", items.get("2")); + assertEquals("c", items.get("3")); + assertEquals(" ", items.get("4")); + assertTrue(items instanceof TreeMap); + } + + @Test + public void testCollection_HashSet(){ + Configuration config = Configuration.current(); + Collection<String> items = config.get("typed2.hashset", new TypeLiteral<Collection<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof HashSet); + items = (Collection<String>) config.get("typed2.hashset", Collection.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof HashSet); + } + + @Test + public void testCollection_TreeSet(){ + Configuration config = Configuration.current(); + Collection<String> items = config.get("typed2.treeset", new TypeLiteral<Collection<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof TreeSet); + items = (Collection<String>) config.get("typed2.treeset", Collection.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof TreeSet); + } + + @Test + public void testCollection_ArrayList(){ + Configuration config = Configuration.current(); + Collection<String> items = config.get("typed2.arraylist", new TypeLiteral<Collection<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof ArrayList); + items = (Collection<String>) config.get("typed2.arraylist", Collection.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof ArrayList); + } + + @Test + public void testCollection_LinkedList(){ + Configuration config = Configuration.current(); + Collection<String> items = config.get("typed2.linkedlist", new TypeLiteral<Collection<String>>(){}); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof LinkedList); + items = (Collection<String>) config.get("typed2.linkedlist", Collection.class); + assertNotNull(items); + assertFalse(items.isEmpty()); + assertEquals(10, items.size()); + assertTrue(items instanceof LinkedList); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/collections/src/test/java/org/apache/tamaya/collections/MyUpperCaseConverter.java ---------------------------------------------------------------------- diff --git a/modules/collections/src/test/java/org/apache/tamaya/collections/MyUpperCaseConverter.java b/modules/collections/src/test/java/org/apache/tamaya/collections/MyUpperCaseConverter.java new file mode 100644 index 0000000..1c95261 --- /dev/null +++ b/modules/collections/src/test/java/org/apache/tamaya/collections/MyUpperCaseConverter.java @@ -0,0 +1,33 @@ +/* + * 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.collections; + +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; + +/** + * Example converter that is used for testing the custom parsing functionality. It sorrounds values with () and + * converts them to uppercase. + */ +public class MyUpperCaseConverter implements PropertyConverter<String>{ + @Override + public String convert(String value, ConversionContext context) { + return "("+value.toUpperCase()+")"; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/collections/src/test/resources/META-INF/javaconfiguration.properties ---------------------------------------------------------------------- diff --git a/modules/collections/src/test/resources/META-INF/javaconfiguration.properties b/modules/collections/src/test/resources/META-INF/javaconfiguration.properties new file mode 100644 index 0000000..3764840 --- /dev/null +++ b/modules/collections/src/test/resources/META-INF/javaconfiguration.properties @@ -0,0 +1,69 @@ +# +# 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. +# +# Similar to etcd all keys starting with a _ are hidden by default (only directly accessible). + +# Config for base tests (no combination policy) +base.items=1,2,3,4,5,6,7,8,9,0 +base.map=1=a, 2=b, 3=c, [4= ] + +# Config for tests with explcit implementation types +typed2.arraylist=1,2,3,4,5,6,7,8,9,0 +[META]typed2.arraylist.collection-type=ArrayList +typed2.linkedlist=1,2,3,4,5,6,7,8,9,0 +[META]typed2.linkedlist.collection-type=java.util.LinkedList +typed2.hashset=1,2,3,4,5,6,7,8,9,0 +[META]typed2.hashset.collection-type=HashSet +typed2.treeset=1,2,3,4,5,6,7,8,9,0 +[META]typed2.treeset.collection-type=TreeSet +typed2.hashmap=1=a, 2=b, 3=c, [4= ] +[META]typed2.hashmap.collection-type=java.util.HashMap +typed2.treemap=1=a, 2=b, 3=c, [4= ] +[META]typed2.treemap.collection-type=TreeMap + +# Config for tests with combination policy, writable +typed.arraylist=1,2,3,4,5,6,7,8,9,0 +[META]typed.arraylist.collection-type=ArrayList +[META]typed.arraylist.read-only=true +typed.linkedlist=1,2,3,4,5,6,7,8,9,0 +[META]typed.linkedlist.collection-type=java.util.LinkedList +typed.hashset=1,2,3,4,5,6,7,8,9,0 +[META]typed.hashset.collection-type=HashSet +typed.treeset=1,2,3,4,5,6,7,8,9,0 +[META]typed.treeset.collection-type=TreeSet +typed.hashmap=1=a, 2=b, 3=c, [4= ] +[META]typed.hashmap.collection-type=java.util.HashMap +[META]typed.hashmap.read-only=true +typed.treemap=1=a, 2=b, 3=c, [4= ] +[META]typed.treemap.collection-type=TreeMap + +# Config for advanced tests +sep-list=a,b,c|d,e,f|g,h,i +[META]sep-list.collection-type=List +[META]sep-list.item-separator=| +currency-list=CHF,USD,USS +[META]currency-list.collection-type=List + +parser-list=a,b,c +[META]parser-list.collection-type=List +[META]parser-list.item-converter=org.apache.tamaya.collections.MyUpperCaseConverter + +redefined-map=0==none | 1==single | 2==any +[META]redefined-map.map-entry-separator=== +[META]redefined-map.item-separator=| + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/bnd.bnd ---------------------------------------------------------------------- diff --git a/modules/consul/bnd.bnd b/modules/consul/bnd.bnd new file mode 100644 index 0000000..9928592 --- /dev/null +++ b/modules/consul/bnd.bnd @@ -0,0 +1,32 @@ +-buildpath: \ + osgi.annotation; version=6.0.0,\ + osgi.core; version=6.0,\ + osgi.cmpn; version=6.0 + +-testpath: \ + ${junit} + +javac.source: 1.8 +javac.target: 1.8 + +Automatic-Module-Name: org.apache.tamaya.consul +Bundle-Version: ${version}.${tstamp} +Bundle-Name: Apache Tamaya - Consul +Bundle-SymbolicName: org.apache.tamaya.consul +Bundle-Description: Apacha Tamaya Config - Consul PropertySource +Bundle-Category: Implementation +Bundle-Copyright: (C) Apache Foundation +Bundle-License: Apache Licence version 2 +Bundle-Vendor: Apache Software Foundation +Bundle-ContactAddress: [email protected] +Bundle-DocURL: http://tamaya.apache.org +Export-Package: \ + org.apache.tamaya.consul +Import-Package: \ + org.apache.tamaya,\ + org.apache.tamaya.spi,\ + org.apache.tamaya.mutableconfig,\ + org.apache.tamaya.mutableconfig.spi +Export-Service: \ + org.apache.tamaya.spi.PropertySource + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/pom.xml ---------------------------------------------------------------------- diff --git a/modules/consul/pom.xml b/modules/consul/pom.xml new file mode 100644 index 0000000..aa8dfe4 --- /dev/null +++ b/modules/consul/pom.xml @@ -0,0 +1,83 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy 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.4-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>tamaya-consul</artifactId> + <name>Apache Tamaya Modules - Consul PropertySource</name> + <packaging>jar</packaging> + + <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.parent.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-functions</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-mutable-config</artifactId> + <version>${project.parent.version}</version> + <scope>provided</scope> + <optional>true</optional> + </dependency> + <dependency> + <groupId>com.orbitz.consul</groupId> + <artifactId>consul-client</artifactId> + <version>0.17.0</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-client</artifactId> + <version>3.2.1</version> + </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-transports-http-hc</artifactId> + <version>3.2.1</version> + </dependency> + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/src/main/java/org/apache/tamaya/consul/AbstractConsulPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/consul/src/main/java/org/apache/tamaya/consul/AbstractConsulPropertySource.java b/modules/consul/src/main/java/org/apache/tamaya/consul/AbstractConsulPropertySource.java new file mode 100644 index 0000000..f07fb68 --- /dev/null +++ b/modules/consul/src/main/java/org/apache/tamaya/consul/AbstractConsulPropertySource.java @@ -0,0 +1,246 @@ +/* + * 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.ConfigChangeRequest; +import org.apache.tamaya.mutableconfig.spi.MutablePropertySource; +import org.apache.tamaya.spi.PropertyValue; +import org.apache.tamaya.spisupport.propertysource.BasePropertySource; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Propertysource base class that is reading configuration from a configured consul endpoint. + */ +public abstract class AbstractConsulPropertySource extends BasePropertySource +implements MutablePropertySource{ + private static final Logger LOG = Logger.getLogger(AbstractConsulPropertySource.class.getName()); + + private String prefix = ""; + + private List<HostAndPort> consulBackends = new ArrayList<>(); + + /** The config cache used. */ + private Map<String, PropertyValue> configMap = new ConcurrentHashMap<>(); + + private AtomicLong timeoutDuration = new AtomicLong(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES)); + + private AtomicLong timeout = new AtomicLong(); + + + public AbstractConsulPropertySource(){ + this("consul"); + } + + public AbstractConsulPropertySource(String name){ + super(name); + } + + /** + * Get the current timeout, when a reload will be triggered on access. + * @return the current timeout, or 0 if no data has been loaded at all. + */ + public long getValidUntil(){ + return timeout.get(); + } + + /** + * Get the current cache timeout. + * @return the timeout duration after which data will be reloaded. + */ + public long getCachePeriod(){ + return timeoutDuration.get(); + } + + /** + * Set the duration after which the data cache will be reloaded. + * @param millis the millis + */ + public void setCacheTimeout(long millis){ + this.timeoutDuration.set(millis); + } + + /** + * Gets the prefix that is added for looking up keys in consul. This allows to use a separate subnamespace in + * consul for configuration. + * @return the prefix, never null. + */ + public String getPrefix() { + return prefix; + } + + /** + * Sets the prefix that is added for looking up keys in consul. This allows to use a separate subnamespace in + * consul for configuration. + * @param prefix the prefix, not null. + */ + public void setPrefix(String prefix) { + this.prefix = Objects.requireNonNull(prefix); + } + + /** + * Set the consol server to connect to. + * @param server the server list, not null. + */ + public void setServer(List<String> server){ + if(!Objects.equals(getServer(), server)) { + List<HostAndPort> consulBackends = new ArrayList<>(); + for (String s : server) { + consulBackends.add(HostAndPort.fromString(s)); + } + this.consulBackends = consulBackends; + refresh(); + } + + } + + /** + * Get a list of current servers. + * @return the server list, not null. + */ + public List<String> getServer() { + return this.consulBackends.stream().map(s -> s.toString()).collect(Collectors.toList()); + } + + /** + * Checks for a cache timeout and optionally reloads the data. + */ + public void checkRefresh(){ + if(this.timeout.get() < System.currentTimeMillis()){ + refresh(); + } + } + + /** + * Clears the cached entries. + */ + public void refresh(){ + this.configMap.clear(); + } + + @Override + public PropertyValue get(String key) { + checkRefresh(); + String reqKey = key; + if(key.startsWith("[META]")){ + reqKey = key.substring("[META]".length()); + 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()); + } + } + PropertyValue val = this.configMap.get(reqKey); + if(val!=null){ + return val; + } + // 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 + for(HostAndPort hostAndPort: this.consulBackends){ + try{ + Consul consul = Consul.builder().withHostAndPort(hostAndPort).build(); + KeyValueClient kvClient = consul.keyValueClient(); + Optional<Value> valueOpt = kvClient.getValue(prefix + reqKey); + if(!valueOpt.isPresent()) { + LOG.log(Level.FINE, "key not found in consul: " + prefix + reqKey); + }else{ + // No prefix mapping necessary here, since we only access/return the createValue... + Value value = valueOpt.get(); + Map<String,String> props = new HashMap<>(); + props.put("createIndex", String.valueOf(value.getCreateIndex())); + props.put("modifyIndex", String.valueOf(value.getModifyIndex())); + props.put("lockIndex", String.valueOf(value.getLockIndex())); + props.put("flags", String.valueOf(value.getFlags())); + props.put("source", getName()); + val = PropertyValue.createValue(reqKey, value.getValue().get()) + .setMeta(props); + break; + } + } catch(Exception e){ + LOG.log(Level.FINE, "etcd access failed on " + hostAndPort + ", trying next...", e); + } + } + if(val!=null){ + this.configMap.put(reqKey, val); + } + return val; + } + + @Override + public Map<String, PropertyValue> getProperties() { + checkRefresh(); + return Collections.unmodifiableMap(configMap); + } + + @Override + public void applyChange(ConfigChangeRequest configChange) { + for(HostAndPort hostAndPort: this.consulBackends){ + 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(prefix + key,en.getValue()); + }catch(Exception e) { + LOG.info("Failed to add key to consul: " + prefix + en.getKey() + "=" + en.getValue()); + } + } + // success: stop here + break; + } catch(Exception e){ + LOG.log(Level.FINE, "consul access failed on " + hostAndPort + ", trying next...", e); + } + } + } + + @Override + protected String toStringValues() { + return super.toStringValues() + + " prefix=" + prefix + '\n' + + " cacheTimeout=" + timeout + '\n' + + " backends=" + this.consulBackends + '\n'; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulBackendConfig.java ---------------------------------------------------------------------- diff --git a/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulBackendConfig.java b/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulBackendConfig.java new file mode 100644 index 0000000..c7b0c14 --- /dev/null +++ b/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulBackendConfig.java @@ -0,0 +1,85 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +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. + */ +final class ConsulBackendConfig { + + private static final Logger LOG = Logger.getLogger(ConsulBackendConfig.class.getName()); + private static final String TAMAYA_CONSUL_SERVER_URLS = "tamaya.consul.server.urls"; + private static final String TAMAYA_CONSUL_DIRECTORY = "tamaya.consul.directory"; + private static final String TAMAYA_CONSUL_PREFIX = "tamaya.consul.prefix"; + + + private ConsulBackendConfig(){} + + public static String getConsulDirectory(){ + String val = System.getProperty(TAMAYA_CONSUL_DIRECTORY); + if(val == null){ + val = System.getenv(TAMAYA_CONSUL_DIRECTORY); + } + if(val!=null){ + return val; + } + return ""; + } + + public static List<String> getServers(){ + String serverURLs = System.getProperty(TAMAYA_CONSUL_SERVER_URLS); + if(serverURLs==null){ + serverURLs = System.getenv(TAMAYA_CONSUL_SERVER_URLS); + } + if(serverURLs==null){ + serverURLs = "http://127.0.0.1:4001"; + } + List<String> servers = new ArrayList<>(); + for(String url:serverURLs.split("\\,")) { + try{ + servers.add(url.trim()); + LOG.info("Using etcd endoint: " + url); + } catch(Exception e){ + LOG.log(Level.SEVERE, "Error initializing etcd accessor for URL: " + url, e); + } + } + return servers; + } + + public static String getPrefix() { + String val = System.getProperty(TAMAYA_CONSUL_PREFIX); + if(val == null){ + val = System.getenv(TAMAYA_CONSUL_PREFIX); + } + if(val!=null){ + return val; + } + return ""; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java b/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java new file mode 100644 index 0000000..ce92a95 --- /dev/null +++ b/modules/consul/src/main/java/org/apache/tamaya/consul/ConsulPropertySource.java @@ -0,0 +1,66 @@ +/* + * 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.ConfigChangeRequest; +import org.apache.tamaya.mutableconfig.spi.MutablePropertySource; +import org.apache.tamaya.spi.PropertyValue; +import org.apache.tamaya.spisupport.propertysource.BasePropertySource; + +import java.util.*; +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 configuration + * to this prefix namespace. Consul servers are configured as {@code consul.urls} system or environment property. + */ +public class ConsulPropertySource extends AbstractConsulPropertySource{ + private static final Logger LOG = Logger.getLogger(ConsulPropertySource.class.getName()); + + + public ConsulPropertySource(String prefix, List<String> backends){ + this(); + setPrefix(prefix); + setServer(backends); + } + + public ConsulPropertySource(List<String> backends){ + this(); + setServer(backends); + } + + public ConsulPropertySource(){ + super(); + setDefaultOrdinal(1000); + setPrefix(System.getProperty("tamaya.consul.prefix", "")); + } + + public ConsulPropertySource(String... backends){ + this(); + setServer(Arrays.asList(backends)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulPropertySourceTest.java ---------------------------------------------------------------------- diff --git a/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulPropertySourceTest.java b/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulPropertySourceTest.java new file mode 100644 index 0000000..7b04dc0 --- /dev/null +++ b/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulPropertySourceTest.java @@ -0,0 +1,72 @@ +/* + * 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 org.apache.tamaya.consul.ConsulPropertySource; +import org.apache.tamaya.spi.PropertyValue; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +/** + * Created by atsticks on 07.01.16. + */ +public class ConsulPropertySourceTest { + + private final ConsulPropertySource propertySource = new ConsulPropertySource(); + + @BeforeClass + public static void setup(){ + System.setProperty("consul.urls", "http://127.0.0.1:8300"); + } + + @Test + public void testGetOrdinal() throws Exception { + assertEquals(1000, propertySource.getOrdinal()); + } + + @Test + public void testGetDefaultOrdinal() throws Exception { + assertEquals(1000, propertySource.getDefaultOrdinal()); + } + + @Test + public void testGetName() throws Exception { + assertEquals("consul", propertySource.getName()); + } + + @Test + public void testGet() throws Exception { + Map<String,PropertyValue> props = propertySource.getProperties(); + for(Map.Entry<String,PropertyValue> en:props.entrySet()){ + assertNotNull("Key not found: " + en.getKey(), propertySource.get(en.getKey())); + } + } + + @Test + public void testGetProperties() throws Exception { + Map<String,PropertyValue> props = propertySource.getProperties(); + assertNotNull(props); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulWriteTest.java ---------------------------------------------------------------------- diff --git a/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulWriteTest.java b/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulWriteTest.java new file mode 100644 index 0000000..1746030 --- /dev/null +++ b/modules/consul/src/test/java/org/apache/tamaya/consul/ConsulWriteTest.java @@ -0,0 +1,86 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.UUID; + +import org.apache.tamaya.mutableconfig.ConfigChangeRequest; +import org.apache.tamaya.spi.PropertyValue; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests for the consul backend integration for writing to the consul backend. + */ +public class ConsulWriteTest { + + /** + * Needs to be enabled manually in case you want to do integration tests. + */ + static boolean execute = false; + private static ConsulPropertySource propertySource; + + @BeforeClass + public static void setup() throws MalformedURLException, URISyntaxException { + System.setProperty("consul.urls", "http://127.0.0.1:8300"); + propertySource = new ConsulPropertySource(); + + System.out.println("At the moment no write-tests can be executed to verify the Consul integration. You can manually edit this test class."); + } + + @Test + public void testSetNormal() throws Exception { + if (!execute) return; + String taID = UUID.randomUUID().toString(); + ConfigChangeRequest request = new ConfigChangeRequest("testSetNormal"); + request.put(taID, "testSetNormal"); + propertySource.applyChange(request); + } + + + @Test + public void testDelete() throws Exception { + if(!execute)return; + String taID = UUID.randomUUID().toString(); + ConfigChangeRequest request = new ConfigChangeRequest("testDelete"); + request.put(taID, "testDelete"); + propertySource.applyChange(request); + assertEquals(taID.toString(), propertySource.get("testDelete").getValue()); + assertNotNull(propertySource.get("_testDelete.createdIndex")); + request = new ConfigChangeRequest("testDelete2"); + request.remove("testDelete"); + propertySource.applyChange(request); + assertNull(propertySource.get("testDelete")); + } + + @Test + public void testGetProperties() throws Exception { + if(!execute)return; + Map<String,PropertyValue> result = propertySource.getProperties(); + assertTrue(result.isEmpty()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/etcd/bnd.bnd ---------------------------------------------------------------------- diff --git a/modules/etcd/bnd.bnd b/modules/etcd/bnd.bnd new file mode 100644 index 0000000..129f3ec --- /dev/null +++ b/modules/etcd/bnd.bnd @@ -0,0 +1,32 @@ +-buildpath: \ + osgi.annotation; version=6.0.0,\ + osgi.core; version=6.0,\ + osgi.cmpn; version=6.0 + +-testpath: \ + ${junit} + +javac.source: 1.8 +javac.target: 1.8 + +Automatic-Module-Name: org.apache.tamaya.etcd +Bundle-Version: ${version}.${tstamp} +Bundle-Name: Apache Tamaya - Etcd Config +Bundle-SymbolicName: org.apache.tamaya.etcd +Bundle-Description: Apacha Tamaya Config - Etcd PropertySource +Bundle-Category: Implementation +Bundle-Copyright: (C) Apache Foundation +Bundle-License: Apache Licence version 2 +Bundle-Vendor: Apache Software Foundation +Bundle-ContactAddress: [email protected] +Bundle-DocURL: http://tamaya.apache.org +Export-Package: \ + org.apache.tamaya.etcd +Import-Package: \ + org.apache.tamaya,\ + org.apache.tamaya.spi,\ + org.apache.tamaya.mutableconfig\ + org.apache.tamaya.mutableconfig.spi +Export-Service: \ + org.apache.tamaya.spi.PropertySource + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/etcd/pom.xml ---------------------------------------------------------------------- diff --git a/modules/etcd/pom.xml b/modules/etcd/pom.xml new file mode 100644 index 0000000..f8a9361 --- /dev/null +++ b/modules/etcd/pom.xml @@ -0,0 +1,80 @@ +<!-- +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.4-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>tamaya-etcd</artifactId> + <name>Apache Tamaya Modules - etcd PropertySource</name> + <packaging>jar</packaging> + + <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.parent.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-functions</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient-osgi</artifactId> + <version>4.5.3</version> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-json_1.1_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.parent.version}</version> + <scope>provided</scope> + <optional>true</optional> + </dependency> + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/etcd/src/main/java/org/apache/tamaya/etcd/AbstractEtcdPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/etcd/src/main/java/org/apache/tamaya/etcd/AbstractEtcdPropertySource.java b/modules/etcd/src/main/java/org/apache/tamaya/etcd/AbstractEtcdPropertySource.java new file mode 100644 index 0000000..3388b8e --- /dev/null +++ b/modules/etcd/src/main/java/org/apache/tamaya/etcd/AbstractEtcdPropertySource.java @@ -0,0 +1,267 @@ +/* + * 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.ConfigChangeRequest; +import org.apache.tamaya.mutableconfig.spi.MutablePropertySource; +import org.apache.tamaya.spi.PropertyValue; +import org.apache.tamaya.spisupport.propertysource.BasePropertySource; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +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 configuration + * 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 environment or system property. + */ +public abstract class AbstractEtcdPropertySource extends BasePropertySource + implements MutablePropertySource{ + + private static final Logger LOG = Logger.getLogger(AbstractEtcdPropertySource.class.getName()); + + private String directory =""; + + private List<String> servers = new ArrayList<>(); + + private List<EtcdAccessor> etcdBackends = new ArrayList<>(); + + private Map<String,String> metaData = new HashMap<>(); + + private AtomicLong timeoutDuration = new AtomicLong(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES)); + + private AtomicLong timeout = new AtomicLong(); + + /** The Hazelcast config map used. */ + private Map<String, PropertyValue> configMap = new HashMap<>(); + + public AbstractEtcdPropertySource(){ + this("etcd"); + } + + public AbstractEtcdPropertySource(String name){ + super(name); + metaData.put("source", "etcd"); + } + + /** + * Get the current timeout, when a reload will be triggered on access. + * @return the current timeout, or 0 if no data has been loaded at all. + */ + public long getValidUntil(){ + return timeout.get(); + } + + /** + * Get the current cache timeout. + * @return the timeout duration after which data will be reloaded. + */ + public long getCachePeriod(){ + return timeoutDuration.get(); + } + + /** + * Set the duration after which the data cache will be reloaded. + * @param millis the millis + */ + public void setCacheTimeout(long millis){ + this.timeoutDuration.set(millis); + } + + /** + * Get the etc directora accessed. + * @return the etc director, not null. + */ + public String getDirectory() { + return directory; + } + + /** + * Sets the etcd directory to read from. + * @param directory the directory, not null. + */ + public void setDirectory(String directory) { + if(!Objects.equals(this.directory, directory)) { + this.directory = Objects.requireNonNull(directory); + refresh(); + } + } + + public void setServer(List<String> servers) { + if(!Objects.equals(this.servers, servers)) { + List<EtcdAccessor> etcdBackends = new ArrayList<>(); + for (String s : servers) { + etcdBackends.add(new EtcdAccessor(s)); + } + this.servers = Collections.unmodifiableList(servers); + this.etcdBackends = etcdBackends; + metaData.put("backends", servers.toString()); + refresh(); + } + } + + /** + * Get the underlying servers this instance will try to connect to. + * @return the server list, not null. + */ + public List<String> getServer(){ + return servers; + } + + /** + * Checks for a cache timeout and optionally reloads the data. + */ + public void checkRefresh(){ + if(this.timeout.get() < System.currentTimeMillis()){ + refresh(); + } + } + + /** + * Reloads the data and updated the cache timeouts. + */ + public void refresh() { + for(EtcdAccessor accessor: this.etcdBackends){ + try{ + Map<String, String> props = accessor.getProperties(directory); + if(!props.containsKey("_ERROR")) { + this.configMap = mapPrefix(props); + this.timeout.set(System.currentTimeMillis() + timeoutDuration.get()); + } 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); + } + } + } + + @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(); + } + + @Override + public PropertyValue get(String key) { + checkRefresh(); + return configMap.get(key); + } + + @Override + public Map<String, PropertyValue> getProperties() { + checkRefresh(); + return configMap; + } + + private Map<String, PropertyValue> mapPrefix(Map<String, String> props) { + + Map<String, PropertyValue> values = new HashMap<>(); + // Evaluate keys + for(Map.Entry<String,String> entry:props.entrySet()) { + if (!entry.getKey().startsWith("_")) { + PropertyValue val = values.get(entry.getKey()); + if (val == null) { + val = PropertyValue.createValue(entry.getKey(), "").setMeta("source", getName()).setMeta(metaData); + values.put(entry.getKey(), val); + } + } + } + // add getMeta entries + for(Map.Entry<String,String> entry:props.entrySet()) { + if (entry.getKey().startsWith("_")) { + String key = entry.getKey().substring(1); + for(String field:new String[]{".createdIndex", ".modifiedIndex", ".ttl", + ".expiration", ".source"}) { + if (key.endsWith(field)) { + key = key.substring(0, key.length() - field.length()); + PropertyValue val = values.get(key); + if (val != null) { + val.setMeta(field, entry.getValue()); + } + } + } + } + } + // Map to createValue map. +// Map<String, PropertyValue> values = new HashMap<>(); + for(Map.Entry<String,PropertyValue> en:values.entrySet()) { + values.put(en.getKey(), en.getValue()); + } + return values; + } + + @Override + public void applyChange(ConfigChangeRequest configChange) { + for(EtcdAccessor accessor: etcdBackends){ + 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); + } + } + } + + + @Override + protected String toStringValues() { + return super.toStringValues() + + " directory=" + directory + '\n' + + " servers=" + this.servers + '\n'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java ---------------------------------------------------------------------- diff --git a/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java b/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java new file mode 100644 index 0000000..3317638 --- /dev/null +++ b/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdAccessor.java @@ -0,0 +1,519 @@ +/* + * 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. + */ +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 makes Johnzon accept comments. + */ + 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, createValue pair. Hereby the response returned from + * etcd: + * + * <pre> + * { + * "action": "current", + * "getField": { + * "createdIndex": 2, + * "key": "/message", + * "modifiedIndex": 2, + * "createValue": "Hello world" + * } + * } + * </pre> + * + * is mapped to: + * + * <pre> + * key=createValue + * _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 getMeta-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("getField"); + if (node.containsKey("createValue")) { + result.put(key, node.getString("createValue")); + 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 setCurrent. + * + * @param key the property key, not null + * @param value the createValue to be setCurrent + * @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": "setCurrent", + * "getField": { + * "createdIndex": 3, + * "key": "/message", + * "modifiedIndex": 3, + * "createValue": "Hello etcd" + * }, + * "prevNode": { + * "createdIndex": 2, + * "key": "/message", + * "createValue": "Hello world", + * "modifiedIndex": 2 + * } + * } + * </pre> + * + * is mapped to: + * + * <pre> + * key=createValue + * _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 createValue to be setCurrent + * @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("createValue", 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("getField"); + 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("createValue")); + 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.createValue=... + * </pre> + * + * @param key the key to be deleted. + * @return the response maps 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("getField"); + 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.createValue", prevNode.getString("createValue")); + } + } + + /** + * 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": "current", + * "getField": { + * "key": "/", + * "dir": true, + * "getList": [ + * { + * "key": "/foo_dir", + * "dir": true, + * "modifiedIndex": 2, + * "createdIndex": 2 + * }, + * { + * "key": "/foo", + * "createValue": "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 setCurrent 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("getField"); + 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 getField 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("createValue")); + 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("getList"); + 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-extensions/blob/c8ba9c4c/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackendConfig.java ---------------------------------------------------------------------- diff --git a/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackendConfig.java b/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackendConfig.java new file mode 100644 index 0000000..95561c9 --- /dev/null +++ b/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdBackendConfig.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.etcd; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Singleton that reads the current etcd setup, especially the possible URLs to be used. + */ +final class EtcdBackendConfig { + + private static final Logger LOG = Logger.getLogger(EtcdBackendConfig.class.getName()); + private static final String TAMAYA_ETCD_SERVER_URLS = "tamaya.etcd.server"; + private static final String TAMAYA_ETCD_TIMEOUT = "tamaya.etcd.timeout"; + private static final String TAMAYA_ETCD_DIRECTORY = "tamaya.etcd.directory"; + + + private EtcdBackendConfig(){} + + /** + * Get the default etcd directory selector, default {@code ""}. + * @return the default etcd directory selector, never null. + */ + public static String getEtcdDirectory(){ + String val = System.getProperty(TAMAYA_ETCD_DIRECTORY); + if(val == null){ + val = System.getenv(TAMAYA_ETCD_DIRECTORY); + } + if(val!=null){ + return val; + } + return ""; + } + + /** + * Get the etcd connection timeout from system/enfironment property {@code tamaya.etcd.timeout (=seconds)} + * (default 2 seconds). + * @return the etcd connection timeout. + */ + public static long getEtcdTimeout(){ + String val = System.getProperty(TAMAYA_ETCD_TIMEOUT); + if(val == null){ + val = System.getenv(TAMAYA_ETCD_TIMEOUT); + } + if(val!=null){ + return TimeUnit.MILLISECONDS.convert(Integer.parseInt(val), TimeUnit.SECONDS); + } + return 2000L; + } + + /** + * Evaluate the etcd target servers fomr system/environment property {@code tamaya.etcd.server}. + * @return the servers configured, or {@code http://127.0.0.1:4001} (default). + */ + public static List<String> getServers(){ + 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"; + } + List<String> servers = new ArrayList<>(); + for(String url:serverURLs.split("\\,")) { + try{ + servers.add(url.trim()); + LOG.info("Using etcd endoint: " + url); + } catch(Exception e){ + LOG.log(Level.SEVERE, "Error initializing etcd accessor for URL: " + url, e); + } + } + return servers; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/c8ba9c4c/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java ---------------------------------------------------------------------- diff --git a/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java b/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java new file mode 100644 index 0000000..d168ab0 --- /dev/null +++ b/modules/etcd/src/main/java/org/apache/tamaya/etcd/EtcdPropertySource.java @@ -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 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.spi.PropertyValue; + +import java.util.*; +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 configuration + * 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 environment or system property. + */ +public class EtcdPropertySource extends AbstractEtcdPropertySource{ + + private static final Logger LOG = Logger.getLogger(EtcdPropertySource.class.getName()); + + public EtcdPropertySource(List<String> server){ + this(); + setServer(server); + } + + public EtcdPropertySource(String... server){ + this(); + setServer(Arrays.asList(server)); + } + + public EtcdPropertySource(){ + setDefaultOrdinal(1000); + setDirectory(EtcdBackendConfig.getEtcdDirectory()); + setServer(EtcdBackendConfig.getServers()); + } + +}
