
com.netflix.archaius.persisted2.JsonPersistedV2Reader Maven / Gradle / Ivy
package com.netflix.archaius.persisted2;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.archaius.config.polling.PollingResponse;
/**
* Reader for Netflix persisted properties (not yet available in OSS).
*
* Properties are read as a single JSON blob that contains a full list of properties.
* Multiple property values may exist for different scopes and are resolved into
* a single property value using a ProperyValueResolver.
*
* Example,
*
*
* {@code
* JsonPersistedV2Reader reader =
* JsonPersistedV2Reader.builder(new HTTPStreamLoader(url)) // url from which config is fetched
* .withPredicate(ScopePredicates.fromMap(instanceScopes)) // Map of scope values for the running instance
* .build();
*
* appConfig.addConfigFirst(new PollingDynamicConfig("dyn", reader, new FixedPollingStrategy(30, TimeUnit.SECONDS)));
*
* }
*
*
* @author elandau
*
*/
public class JsonPersistedV2Reader implements Callable {
private static final Logger LOG = LoggerFactory.getLogger(JsonPersistedV2Reader.class);
private final static List DEFAULT_ORDERED_SCOPES = Arrays.asList("serverId", "asg", "ami", "cluster", "appId", "env", "countries", "stack", "zone", "region");
private final static String DEFAULT_KEY_FIELD = "key";
private final static String DEFAULT_VALUE_FIELD = "value";
private final static List DEFAULT_PATH = Arrays.asList("persistedproperties", "properties", "property");
public static class Builder {
private final Callable reader;
private List path = DEFAULT_PATH;
private List scopeFields = DEFAULT_ORDERED_SCOPES;
private String keyField = DEFAULT_KEY_FIELD;
private String valueField = DEFAULT_VALUE_FIELD;
private ScopePredicate predicate = ScopePredicates.alwaysTrue();
private ScopedValueResolver resolver = new ScopePriorityPropertyValueResolver();
public Builder(Callable reader) {
this.reader = reader;
}
public Builder withPath(String path) {
return withPath(Arrays.asList(StringUtils.split(path, "/")));
}
public Builder withPath(List path) {
List copy = new ArrayList();
copy.addAll(path);
this.path = Collections.unmodifiableList(copy);
return this;
}
public Builder withScopes(List scopes) {
this.scopeFields = scopes;
return this;
}
public Builder withPredicate(ScopePredicate predicate) {
this.predicate = predicate;
return this;
}
public Builder withKeyField(String keyField) {
this.keyField = keyField;
return this;
}
public Builder withValueField(String valueField) {
this.valueField = valueField;
return this;
}
public Builder withValueResolver(ScopedValueResolver resolver) {
this.resolver = resolver;
return this;
}
public JsonPersistedV2Reader build() {
return new JsonPersistedV2Reader(this);
}
}
public static Builder builder(Callable reader) {
return new Builder(reader);
}
private final Callable reader;
private final ScopePredicate predicate;
private final ScopedValueResolver valueResolver;
private final ObjectMapper mapper = new ObjectMapper();
private final List scopeFields;
private final String keyField;
private final String valueField;
private final List path;
private JsonPersistedV2Reader(Builder builder) {
this.reader = builder.reader;
this.predicate = builder.predicate;
this.valueResolver = builder.resolver;
this.keyField = builder.keyField;
this.valueField = builder.valueField;
this.scopeFields = builder.scopeFields;
this.path = builder.path;
}
@Override
public PollingResponse call() throws Exception {
Map> props = new HashMap>();
InputStream is = reader.call();
if (is == null) {
return PollingResponse.noop();
}
try {
JsonNode node = mapper.readTree(is);
for (String part : this.path) {
node = node.path(part);
}
for (final JsonNode property : node) {
String key = null;
try {
key = property.get(keyField).asText();
String value = property.has(valueField) ? property.get(valueField).asText() : "";
LinkedHashMap> scopes = new LinkedHashMap>();
for (String scope : this.scopeFields) {
String[] values = StringUtils.splitByWholeSeparator(property.has(scope) ? property.get(scope).asText().toLowerCase() : "", ",");
scopes.put(scope, values.length == 0 ? Collections.emptySet() : immutableSetFrom(values));
}
// Filter out scopes that don't match at all
if (!this.predicate.evaluate(scopes)) {
continue;
}
// Build up a list of valid scopes
List variations = props.get(key);
if (variations == null) {
variations = new ArrayList();
props.put(key, variations);
}
variations.add(new ScopedValue(value, scopes));
}
catch (Exception e) {
LOG.warn("Unable to process property '{}'", key);
}
}
}
finally {
try {
is.close();
}
catch (Exception e) {
// OK to ignore
}
}
// Resolve to a single property value
final Map result = new HashMap();
for (Entry> entry : props.entrySet()) {
result.put(entry.getKey(), valueResolver.resolve(entry.getKey(), entry.getValue()));
}
return PollingResponse.forSnapshot(result);
}
private static Set immutableSetFrom(String[] values) {
if (values.length == 0) {
return Collections.emptySet();
}
else {
HashSet set = new HashSet();
set.addAll(Arrays.asList(values));
return Collections.unmodifiableSet(set);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy