Skip to content

Commit

Permalink
Split admin config into separate file
Browse files Browse the repository at this point in the history
See #84
  • Loading branch information
trask committed Apr 18, 2016
1 parent 7f01ae5 commit 60aaf13
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,93 +37,123 @@
import org.glowroot.common.util.ObjectMappers;
import org.glowroot.common.util.OnlyUsedByTests;

// TODO if config.json file has unrecognized top-level node (something other than "transactions",
// "userRecording", "advanced", etc) then log warning and remove that node
// TODO if config.json or admin.json file have unrecognized top-level node (something other than
// "transactions", "userRecording", "advanced", etc) then log warning and remove that node
class ConfigFile {

private static final Logger logger = LoggerFactory.getLogger(ConfigFile.class);
private static final ObjectMapper mapper = ObjectMappers.create();

private final File file;
private final ObjectNode rootObjectNode;
private final File configFile;
private final File adminFile;
private final ObjectNode configRootObjectNode;
private final ObjectNode adminRootObjectNode;

ConfigFile(File file) {
this.file = file;
if (!file.exists()) {
rootObjectNode = mapper.createObjectNode();
return;
ConfigFile(File configFile, File adminFile) {
this.configFile = configFile;
this.adminFile = adminFile;
if (configFile.exists()) {
configRootObjectNode = getRootObjectNode(configFile);
} else {
configRootObjectNode = mapper.createObjectNode();
}
String content;
try {
content = Files.toString(file, Charsets.UTF_8);
} catch (IOException e) {
logger.error(e.getMessage(), e);
rootObjectNode = mapper.createObjectNode();
return;
if (adminFile.exists()) {
adminRootObjectNode = getRootObjectNode(adminFile);
} else {
adminRootObjectNode = mapper.createObjectNode();
}
}

@Nullable
<T> T getConfigNode(String key, Class<T> clazz, ObjectMapper mapper) {
JsonNode node = configRootObjectNode.get(key);
if (node == null) {
return null;
}
ObjectNode rootObjectNode = null;
try {
JsonNode rootNode = mapper.readTree(content);
if (rootNode instanceof ObjectNode) {
rootObjectNode = (ObjectNode) rootNode;
}
} catch (IOException e) {
logger.warn("error processing config file: {}", file.getAbsolutePath(), e);
File backupFile = new File(file.getParentFile(), file.getName() + ".invalid-orig");
try {
Files.copy(file, backupFile);
logger.warn("due to an error in the config file, it has been backed up to extension"
+ " '.invalid-orig' and will be overwritten with the default config");
} catch (IOException f) {
logger.warn("error making a copy of the invalid config file before overwriting it",
f);
}
return mapper.treeToValue(node, clazz);
} catch (JsonProcessingException e) {
logger.error("error parsing config json node '{}': ", key, e);
return null;
}
this.rootObjectNode = rootObjectNode == null ? mapper.createObjectNode() : rootObjectNode;
}

@Nullable
<T> T getNode(String key, Class<T> clazz, ObjectMapper mapper) {
JsonNode node = rootObjectNode.get(key);
<T> T getAdminNode(String key, Class<T> clazz, ObjectMapper mapper) {
JsonNode node = adminRootObjectNode.get(key);
if (node == null) {
return null;
}
try {
return mapper.treeToValue(node, clazz);
} catch (JsonProcessingException e) {
logger.error("error parsing config node '{}': ", key, e);
logger.error("error parsing admin json node '{}': ", key, e);
return null;
}
}

<T extends /*@NonNull*/ Object> /*@Nullable*/ T getNode(String key,
<T extends /*@NonNull*/ Object> /*@Nullable*/ T getConfigNode(String key,
TypeReference<T> typeReference, ObjectMapper mapper) {
JsonNode node = rootObjectNode.get(key);
JsonNode node = configRootObjectNode.get(key);
if (node == null) {
return null;
}
try {
return mapper.readValue(mapper.treeAsTokens(node), typeReference);
} catch (IOException e) {
logger.error("error parsing config node '{}': ", key, e);
logger.error("error parsing config json node '{}': ", key, e);
return null;
}
}

void write(String key, Object config, ObjectMapper mapper) throws IOException {
rootObjectNode.replace(key, mapper.valueToTree(config));
writeToFileIfNeeded();
<T extends /*@NonNull*/ Object> /*@Nullable*/ T getAdminNode(String key,
TypeReference<T> typeReference, ObjectMapper mapper) {
JsonNode node = adminRootObjectNode.get(key);
if (node == null) {
return null;
}
try {
return mapper.readValue(mapper.treeAsTokens(node), typeReference);
} catch (IOException e) {
logger.error("error parsing admin json node '{}': ", key, e);
return null;
}
}

void write(Map<String, Object> config, ObjectMapper mapper) throws IOException {
void writeConfig(String key, Object config, ObjectMapper mapper) throws IOException {
configRootObjectNode.replace(key, mapper.valueToTree(config));
writeToFileIfNeeded(configFile, configRootObjectNode);
}

void writeAdmin(String key, Object config, ObjectMapper mapper) throws IOException {
adminRootObjectNode.replace(key, mapper.valueToTree(config));
writeToFileIfNeeded(adminFile, adminRootObjectNode);
}

void writeConfig(Map<String, Object> config, ObjectMapper mapper) throws IOException {
for (Entry<String, Object> entry : config.entrySet()) {
configRootObjectNode.replace(entry.getKey(), mapper.valueToTree(entry.getValue()));
}
writeToFileIfNeeded(configFile, configRootObjectNode);
}

void writeAdmin(Map<String, Object> config, ObjectMapper mapper) throws IOException {
for (Entry<String, Object> entry : config.entrySet()) {
rootObjectNode.replace(entry.getKey(), mapper.valueToTree(entry.getValue()));
adminRootObjectNode.replace(entry.getKey(), mapper.valueToTree(entry.getValue()));
}
writeToFileIfNeeded();
writeToFileIfNeeded(adminFile, adminRootObjectNode);
}

private void writeToFileIfNeeded() throws IOException {
String content = writeConfigAsString();
@OnlyUsedByTests
void delete() throws IOException {
if (!configFile.delete()) {
throw new IOException("Could not delete file: " + configFile.getCanonicalPath());
}
}

private static void writeToFileIfNeeded(File file, ObjectNode rootObjectNode)
throws IOException {
String content = writeConfigAsString(rootObjectNode);
if (file.exists()) {
String existingContent = Files.toString(file, Charsets.UTF_8);
if (content.equals(existingContent)) {
Expand All @@ -135,22 +165,44 @@ private void writeToFileIfNeeded() throws IOException {
Files.write(content, file, Charsets.UTF_8);
}

@OnlyUsedByTests
void delete() throws IOException {
if (!file.delete()) {
throw new IOException("Could not delete file: " + file.getCanonicalPath());
}
}

private String writeConfigAsString() throws IOException {
ObjectNode rootObjectNode = this.rootObjectNode.deepCopy();
ObjectMappers.stripEmptyContainerNodes(rootObjectNode);
private static String writeConfigAsString(ObjectNode rootObjectNode) throws IOException {
ObjectNode rootObjectNodeCopy = rootObjectNode.deepCopy();
ObjectMappers.stripEmptyContainerNodes(rootObjectNodeCopy);
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb))
.setPrettyPrinter(ObjectMappers.getPrettyPrinter());
jg.writeTree(rootObjectNode);
jg.writeTree(rootObjectNodeCopy);
jg.close();
// newline is not required, just a personal preference
return sb.toString() + ObjectMappers.NEWLINE;
}

private static ObjectNode getRootObjectNode(File file) {
String content;
try {
content = Files.toString(file, Charsets.UTF_8);
} catch (IOException e) {
logger.error(e.getMessage(), e);
return mapper.createObjectNode();
}
ObjectNode rootObjectNode = null;
try {
JsonNode rootNode = mapper.readTree(content);
if (rootNode instanceof ObjectNode) {
rootObjectNode = (ObjectNode) rootNode;
}
} catch (IOException e) {
logger.warn("error processing config file: {}", file.getAbsolutePath(), e);
File backupFile = new File(file.getParentFile(), file.getName() + ".invalid-orig");
try {
Files.copy(file, backupFile);
logger.warn("due to an error in the config file, it has been backed up to extension"
+ " '.invalid-orig' and will be overwritten with the default config");
} catch (IOException f) {
logger.warn("error making a copy of the invalid config file before overwriting it",
f);
}
}
return rootObjectNode == null ? mapper.createObjectNode() : rootObjectNode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,55 +83,57 @@ public static ConfigService create(File baseDir, List<PluginDescriptor> pluginDe
}

private ConfigService(File baseDir, List<PluginDescriptor> pluginDescriptors) {
configFile = new ConfigFile(new File(baseDir, "config.json"));
configFile =
new ConfigFile(new File(baseDir, "config.json"), new File(baseDir, "admin.json"));
this.pluginDescriptors = ImmutableList.copyOf(pluginDescriptors);
TransactionConfig transactionConfig =
configFile.getNode("transactions", ImmutableTransactionConfig.class, mapper);
configFile.getConfigNode("transactions", ImmutableTransactionConfig.class, mapper);
if (transactionConfig == null) {
this.transactionConfig = ImmutableTransactionConfig.builder().build();
} else {
this.transactionConfig = transactionConfig;
}
UiConfig uiConfig = configFile.getNode("ui", ImmutableUiConfig.class, mapper);
UiConfig uiConfig = configFile.getConfigNode("ui", ImmutableUiConfig.class, mapper);
if (uiConfig == null) {
this.uiConfig = ImmutableUiConfig.builder().build();
} else {
this.uiConfig = uiConfig;
}
UserRecordingConfig userRecordingConfig =
configFile.getNode("userRecording", ImmutableUserRecordingConfig.class, mapper);
configFile.getConfigNode("userRecording", ImmutableUserRecordingConfig.class,
mapper);
if (userRecordingConfig == null) {
this.userRecordingConfig = ImmutableUserRecordingConfig.builder().build();
} else {
this.userRecordingConfig = userRecordingConfig;
}
AdvancedConfig advancedConfig =
configFile.getNode("advanced", ImmutableAdvancedConfig.class, mapper);
configFile.getConfigNode("advanced", ImmutableAdvancedConfig.class, mapper);
if (advancedConfig == null) {
this.advancedConfig = ImmutableAdvancedConfig.builder().build();
} else {
this.advancedConfig = advancedConfig;
}
List<ImmutableGaugeConfig> gaugeConfigs = configFile.getNode("gauges",
List<ImmutableGaugeConfig> gaugeConfigs = configFile.getConfigNode("gauges",
new TypeReference<List<ImmutableGaugeConfig>>() {}, mapper);
if (gaugeConfigs == null) {
this.gaugeConfigs = getDefaultGaugeConfigs();
} else {
this.gaugeConfigs = ImmutableList.<GaugeConfig>copyOf(gaugeConfigs);
}
List<ImmutableAlertConfig> alertConfigs = configFile.getNode("alerts",
List<ImmutableAlertConfig> alertConfigs = configFile.getConfigNode("alerts",
new TypeReference<List<ImmutableAlertConfig>>() {}, mapper);
if (alertConfigs == null) {
this.alertConfigs = ImmutableList.of();
} else {
this.alertConfigs = ImmutableList.<AlertConfig>copyOf(alertConfigs);
}
List<ImmutablePluginConfigTemp> pluginConfigs = configFile.getNode("plugins",
List<ImmutablePluginConfigTemp> pluginConfigs = configFile.getConfigNode("plugins",
new TypeReference<List<ImmutablePluginConfigTemp>>() {}, mapper);
this.pluginConfigs = fixPluginConfigs(pluginConfigs, pluginDescriptors);

List<ImmutableInstrumentationConfig> instrumentationConfigs =
configFile.getNode("instrumentation",
configFile.getConfigNode("instrumentation",
new TypeReference<List<ImmutableInstrumentationConfig>>() {}, mapper);
if (instrumentationConfigs == null) {
this.instrumentationConfigs = ImmutableList.of();
Expand Down Expand Up @@ -233,72 +235,71 @@ public void addPluginConfigListener(ConfigListener listener) {
}

public void updateTransactionConfig(TransactionConfig updatedConfig) throws IOException {
configFile.write("transactions", updatedConfig, mapper);
configFile.writeConfig("transactions", updatedConfig, mapper);
transactionConfig = updatedConfig;
notifyConfigListeners();
}

public void updateGaugeConfigs(List<GaugeConfig> updatedConfigs) throws IOException {
configFile.write("gauges", updatedConfigs, mapper);
configFile.writeConfig("gauges", updatedConfigs, mapper);
gaugeConfigs = ImmutableList.copyOf(updatedConfigs);
notifyConfigListeners();
}

public void updateAlertConfigs(List<AlertConfig> updatedConfigs) throws IOException {
configFile.write("alerts", updatedConfigs, mapper);
configFile.writeConfig("alerts", updatedConfigs, mapper);
alertConfigs = ImmutableList.copyOf(updatedConfigs);
notifyConfigListeners();
}

public void updateUiConfig(UiConfig updatedConfig) throws IOException {
configFile.write("ui", updatedConfig, mapper);
configFile.writeConfig("ui", updatedConfig, mapper);
uiConfig = updatedConfig;
notifyConfigListeners();
}

public void updatePluginConfigs(List<PluginConfig> updatedConfigs) throws IOException {
ImmutableList<PluginConfig> sortedConfigs =
new PluginConfigOrdering().immutableSortedCopy(updatedConfigs);
configFile.write("plugins", sortedConfigs, mapper);
configFile.writeConfig("plugins", sortedConfigs, mapper);
pluginConfigs = sortedConfigs;
notifyAllPluginConfigListeners();
}

public void updateInstrumentationConfigs(List<InstrumentationConfig> updatedConfigs)
throws IOException {
configFile.write("instrumentation", updatedConfigs, mapper);
configFile.writeConfig("instrumentation", updatedConfigs, mapper);
instrumentationConfigs = ImmutableList.copyOf(updatedConfigs);
notifyConfigListeners();
}

public void updateUserRecordingConfig(UserRecordingConfig updatedConfig) throws IOException {
configFile.write("userRecording", updatedConfig, mapper);
configFile.writeConfig("userRecording", updatedConfig, mapper);
userRecordingConfig = updatedConfig;
notifyConfigListeners();
}

public void updateAdvancedConfig(AdvancedConfig updatedConfig) throws IOException {
configFile.write("advanced", updatedConfig, mapper);
configFile.writeConfig("advanced", updatedConfig, mapper);
advancedConfig = updatedConfig;
notifyConfigListeners();
}

public <T extends /*@NonNull*/ Object> /*@Nullable*/ T getOtherConfig(String key,
Class<T> clazz) {
return configFile.getNode(key, clazz, mapper);
public <T extends /*@NonNull*/ Object> /*@Nullable*/ T getAdmin(String key, Class<T> clazz) {
return configFile.getAdminNode(key, clazz, mapper);
}

public <T extends /*@NonNull*/ Object> /*@Nullable*/ T getOtherConfig(String key,
public <T extends /*@NonNull*/ Object> /*@Nullable*/ T getAdminConfig(String key,
TypeReference<T> typeReference) {
return configFile.getNode(key, typeReference, mapper);
return configFile.getAdminNode(key, typeReference, mapper);
}

public void updateOtherConfig(String key, Object config) throws IOException {
configFile.write(key, config, mapper);
public void updateAdminConfig(String key, Object config) throws IOException {
configFile.writeAdmin(key, config, mapper);
}

public void updateOtherConfigs(Map<String, Object> configs) throws IOException {
configFile.write(configs, mapper);
public void updateAdminConfigs(Map<String, Object> configs) throws IOException {
configFile.writeAdmin(configs, mapper);
}

public boolean readMemoryBarrier() {
Expand Down Expand Up @@ -353,7 +354,7 @@ private void writeAll() throws IOException {
configs.put("alerts", alertConfigs);
configs.put("plugins", pluginConfigs);
configs.put("instrumentation", instrumentationConfigs);
configFile.write(configs, mapper);
configFile.writeConfig(configs, mapper);
}

private static ImmutableList<GaugeConfig> getDefaultGaugeConfigs() {
Expand Down

0 comments on commit 60aaf13

Please sign in to comment.