
org.apache.tamaya.consul.AbstractConsulPropertySource Maven / Gradle / Ivy
The newest version!
/*
* 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 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.ChangeSupport;
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 consulBackends = new ArrayList<>();
/** The config cache used. */
private Map 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 server){
if(!Objects.equals(getServer(), server)) {
List 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 getServer() {
return this.consulBackends.stream().map(HostAndPort::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 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 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().orElse(null))
.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 getProperties() {
checkRefresh();
return Collections.unmodifiableMap(configMap);
}
@Override
public ChangeSupport getChangeSupport(){
return ChangeSupport.UNSUPPORTED;
}
@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 en:configChange.getAddedProperties().entrySet()){
String key = en.getKey();
try{
kvClient.putValue(prefix + key,en.getValue());
}catch(Exception e) {
LOG.info("Failed to addPropertyValue 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';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy