package com.netflix.fabricator.properties;
import java.util.Map;
import java.util.Properties;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.netflix.fabricator.ComponentConfigurationResolver;
import com.netflix.fabricator.ConfigurationNode;
import com.netflix.fabricator.TypeConfigurationResolver;
import com.netflix.fabricator.jackson.JacksonComponentConfiguration;
/**
* TypeConfiguration resolver following the convention of ${id}.${type}. to determine
* the root configuration context for a component
*
* @author elandau
*
*/
public class PropertiesTypeConfigurationResolver implements TypeConfigurationResolver {
private static String DEFAULT_FORMAT_STRING = "%s.%s";
// TOOD: Feature to provide a different field from which to determine the type
// TODO: Feature to provide entirely custom logic to determine the type
private static String TYPE_FIELD = "type";
/**
* Map of type to ComponentConfigurationResolver overrides in the event that the
* naming convention used in this class is not adequate
*/
private final Map<String, ComponentConfigurationResolver> overrides;
/**
* Properties object containing ALL configuration properties for all components
* of varying types
*/
private final Properties properties;
private final ObjectMapper mapper = new ObjectMapper();
@Inject
public PropertiesTypeConfigurationResolver(Properties properties, Map<String, ComponentConfigurationResolver> overrides) {
if (overrides == null) {
overrides = Maps.newHashMap();
}
this.overrides = overrides;
this.properties = properties;
}
@Override
public ComponentConfigurationResolver getConfigurationFactory(final String componentType) {
// Look for overrides
ComponentConfigurationResolver factory = overrides.get(componentType);
if (factory != null)
return factory;
return new ComponentConfigurationResolver() {
@Override
public ConfigurationNode getConfiguration(final String key) {
String prefix = String.format(DEFAULT_FORMAT_STRING, key, componentType);
if (properties.containsKey(prefix)) {
String json = properties.getProperty(prefix).trim();
if (!json.isEmpty() && json.startsWith("{") && json.endsWith("}")) {
try {
JsonNode node = mapper.readTree(json);
if (node.get(TYPE_FIELD) == null)
throw new Exception("Missing 'type' field");
return new JacksonComponentConfiguration(key, node.get(TYPE_FIELD).asText(), node);
} catch (Exception e) {
throw new RuntimeException(
String.format("Unable to parse json from '%s'. (%s)",
prefix,
StringUtils.abbreviate(json, 256)),
e);
}
}
}
String typeField = Joiner.on(".").join(prefix, TYPE_FIELD);
String typeValue = properties.getProperty(typeField);
if (componentType == null) {
throw new RuntimeException(String.format("Type for '%s' not specified '%s'", typeField, componentType));
}
return new PropertiesComponentConfiguration(
key,
typeValue,
properties,
prefix);
}
@Override
public Map<String, ConfigurationNode> getAllConfigurations() {
Map<String, ConfigurationNode> configs = Maps.newHashMap();
for (Object key : properties.keySet()) {
String[] parts = StringUtils.split(key.toString(), ".");
if (parts.length > 1) {
String type = parts[1];
if (type.equals(componentType)) {
String id = parts[0];
if (!configs.containsKey(id)) {
configs.put(id, getConfiguration(id));
}
}
}
}
return configs;
}
};
}
}