com.netflix.iep.config.ConfigFile Maven / Gradle / Ivy
/*
* Copyright 2015 Netflix, Inc.
*
* Licensed 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 com.netflix.iep.config;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Properties;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.Comparator;
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.SimpleBindings;
import javax.script.ScriptException;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
/**
* Helpers for loading configuration properties with scoped blocks.
*
* {{{
* netflix.atlas.foo=1
*
* # Defines a scope, properties after this will only be applied if the condition is true
* # scope: region == "us-east-1" && stack == "main"
* netflix.atlas.foo=1
*
* Properties can be deleted by setting them to null
* netflix.atlas.foo=null
* }}}
*/
public class ConfigFile {
public static boolean checkScope(Map vars, String str) {
ScriptEngineManager mgr = new ScriptEngineManager(null);
ScriptEngine engine = mgr.getEngineByName("javascript");
if (engine == null) throw new IllegalStateException("no javascipt engine found");
SimpleBindings bindings = new SimpleBindings();
for (Map.Entry t : vars.entrySet()) {
bindings.put(t.getKey(), t.getValue());
}
try {
return ((Boolean)engine.eval(str, bindings)).booleanValue();
}
catch (ScriptException e) {
throw new RuntimeException(e);
}
}
/** Load the configuration file using the system environment variables as the `vars`. */
public static Map loadUsingEnv(File file) {
return load(System.getenv(), file);
}
/** Load the configuration file using the system environment variables as the `vars`. */
public static Map loadUsingEnv(String str) {
return load(System.getenv(), str);
}
/** Load the configuration file using the system environment variables as the `vars`. */
public static Properties loadPropertiesUsingEnv(File file) {
return loadProperties(System.getenv(), file);
}
/** Load the configuration file using the system environment variables as the `vars`. */
public static Properties loadPropertiesUsingEnv(String str) {
return loadProperties(System.getenv(), str);
}
public static Map load(Map vars, File file) {
try {
return load(vars, Files.toString(file, Charsets.UTF_8));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Map load(Map vars, String str) {
Properties p = loadProperties(vars, str);
Map m = new HashMap();
for (String n : p.stringPropertyNames()) {
m.put(n, p.getProperty(n));
}
return m;
}
public static Properties loadProperties(Map vars, File file) {
try {
return loadProperties(vars, Files.toString(file, Charsets.UTF_8));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Properties loadProperties(Map vars, String str) {
String propStr = toPropertiesString(vars, str);
Properties p = new Properties();
Reader r = new StringReader(propStr);
try {
p.load(r);
}
catch (IOException e) {
throw new RuntimeException(e);
}
finally {
if (r != null) {
try { r.close(); }
catch (IOException e) {}
}
}
return p;
}
public static String toPropertiesString(Map vars, File file) {
try {
return toPropertiesString(vars, Files.toString(file, Charsets.UTF_8));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
/** Creates a standard java properties string with out of scope properties removed. */
public static String toPropertiesString(Map vars, String str) {
List lines = parse(vars, str);
List inScope = applyOverrides(filterByScope(lines));
List varList = new ArrayList();
Set sortedNames = new TreeSet(vars.keySet());
for (String k : sortedNames) {
varList.add(k + " = [" + vars.get(k) + "]");
}
String varHeader = mkCommentString(varList, "vars", "\n\n");
StringBuffer sb = new StringBuffer(varHeader);
for (ConfigLine cl : inScope) {
sb.append(cl.getLine()).append("\n");
}
return sb.toString();
}
private static List applyOverrides(List lines) {
List props = new ArrayList();
List others = new ArrayList();
for (ConfigLine cl : lines) {
if (cl.isProperty) {
props.add((PropertyLine)cl);
}
else others.add(cl);
}
Comparator clComp = new Comparator() {
@Override public int compare(ConfigLine p1, ConfigLine p2) {
return p1.pos - p2.pos;
}
};
Map> groupedProps = new HashMap>();
for (PropertyLine p : props) {
TreeSet pls = groupedProps.get(p.name);
if (pls == null) {
pls = new TreeSet(clComp);
groupedProps.put(p.name, pls);
}
pls.add(p);
}
TreeSet finalProps = new TreeSet(clComp);
for (Map.Entry> e : groupedProps.entrySet()) {
NavigableSet pls = e.getValue().descendingSet();
PropertyLine pl = pls.pollFirst();
List vs = new ArrayList();
for (PropertyLine p : pls) {
vs.add("[" + p.getScope().expr + "] => [" + p.value + "]");
}
finalProps.add(pl.withOverrides(vs));
}
for (ConfigLine cl : others) {
finalProps.add(cl);
}
return new ArrayList(finalProps);
}
private static List filterByScope(List lines) {
boolean acc1 = true;
List acc2 = new ArrayList();
for (ConfigLine cl : lines) {
if (cl instanceof Scope) {
acc1 = ((Scope)cl).matches;
acc2.add(0, cl);
}
else if (acc1) {
acc2.add(0, cl);
}
}
return acc2;
}
private static List parse(Map vars, String str) {
List config = new ArrayList();
String[] lines = str.split("\n");
Scope scope = new Scope("default", 0, true);
boolean isContinue = false;
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < lines.length; i++) {
String rawLine = lines[i];
String line = rawLine.trim();
if (isContinue) {
buffer.append("\n").append(rawLine);
if (!line.endsWith("\\")) {
config.add(mkProperty(buffer.toString(), i, scope));
buffer = new StringBuilder();
}
}
else if (line.startsWith("# scope:")) {
String expr = line.substring("# scope:".length()).trim();
scope = new Scope(expr, i, checkScope(vars, expr));
config.add(scope);
}
else if (line.startsWith("#")) {
config.add(new Comment(line, i, scope));
}
else if (line.length() == 0) {
config.add(new EmptyLine(line, i, scope));
}
else if (line.endsWith("\\")) {
isContinue = true;
buffer.append(line);
}
else {
config.add(mkProperty(line, i, scope));
}
}
return config;
}
private static String mkCommentString(List seq, String label, String end) {
String sep = "\n# --> ";
StringBuilder sb = new StringBuilder("# ").append(label).append(":");
for (Object o : seq) {
sb.append(sep).append(o.toString());
}
sb.append(end);
return sb.toString();
}
private static ConfigLine mkProperty(String line, int pos, Scope scope) {
int eqPos = line.indexOf("=");
if (eqPos < 1 || eqPos > line.length() - 2)
throw new IllegalStateException("invalid property line: [" + line + "]");
String key = line.substring(0, eqPos).trim();
String value = line.substring(eqPos + 1);
if (value.equals("null")) return new Delete(key, pos, scope);
else return new Property(key, value, pos, scope);
}
private static class ConfigLine {
String line;
int pos;
Scope scope;
boolean isProperty = false;
String getLine() { return line; }
int getPos() { return pos; }
Scope getScope() { return scope; }
}
private static class PropertyLine extends ConfigLine {
String name;
String value;
List overrides;
PropertyLine() {
isProperty = true;
}
String lineString() { throw new UnsupportedOperationException(); }
PropertyLine withOverrides(List overrides) { throw new UnsupportedOperationException(); }
@Override
String getLine() {
if (overrides == null || overrides.size() == 0)
return lineString();
else {
List r = new ArrayList();
for (int i = overrides.size(); i > 0; i--) {
r.add(overrides.get(i - 1));
}
return mkCommentString(r, "overrides", "\n" + lineString());
}
}
}
private static class Scope extends ConfigLine {
String expr;
boolean matches;
Scope(String expr, int pos, boolean matches) {
super();
this.expr = expr;
this.pos = pos;
this.matches = matches;
}
@Override String getLine() { return "# scope: " + expr + " [" + matches + "]"; }
@Override Scope getScope() { return this; }
}
private static class Property extends PropertyLine {
Property(String name, String value, int pos, Scope scope) {
this(name, value, pos, scope, null);
}
Property(String name, String value, int pos, Scope scope, List overrides) {
super();
this.name = name;
this.value = value;
this.pos = pos;
this.scope = scope;
this.overrides = overrides;
}
@Override String lineString() {
return name + "=" + value;
}
@Override PropertyLine withOverrides(List vs) {
return new Property(name, value, pos, scope, vs);
}
}
private static class Delete extends PropertyLine {
Delete(String name, int pos, Scope scope) {
this(name, pos, scope, null);
}
Delete(String name, int pos, Scope scope, List overrides) {
super();
this.name = name;
this.value = null;
this.pos = pos;
this.scope = scope;
this.overrides = overrides;
}
@Override String lineString() { return "# deleted: " + name; }
@Override PropertyLine withOverrides(List vs) {
return new Delete(name, pos, scope, vs);
}
}
private static class Comment extends ConfigLine {
Comment(String line, int pos, Scope scope) {
this.line = line;
this.pos = pos;
this.scope = scope;
}
}
private static class EmptyLine extends ConfigLine {
EmptyLine(String line, int pos, Scope scope) {
this.line = line;
this.pos = pos;
this.scope = scope;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy