package restx.config; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.collect.Iterables; import com.google.common.io.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import restx.common.ConfigElement; import restx.common.RestxConfig; import restx.common.StdRestxConfig; import static com.google.common.io.Files.asCharSource; /** * User: xavierhanin * Date: 9/24/13 * Time: 11:54 PM */ public class ConfigLoader { private final static Logger logger = LoggerFactory.getLogger(ConfigLoader.class); private final String env; public ConfigLoader(Optional<String> env) { this.env = env.or("default"); } /** * Provides a ConfigSupplier loading config from a classpath resource. * * The name of the resource provided must not contain the extension, which must be .properties * * The loader will first try to load config from a file which location is provided by a system * property, the name of the property being the name returned by #locationKeyForResource. * * Then it will try to load an env specific resource named [resource].[env].properties. * * Then it will load a resource named [resource].properties. * * @param resource the path of the resource to load, without extension. * * @return a ConfigSupplier ready to load corresponding resource. */ public ConfigSupplier fromResource(final String resource) { return new ConfigSupplier() { @Override public RestxConfig get() { List<ConfigElement> elements = new ArrayList<>(); loadAllFromResource(elements, resource); return StdRestxConfig.of(elements); } }; } /** * Provides a ConfigSupplier loading config from a file. * * The loader will first try to load an env specific file named [path].[env].properties. * * Then it will try to load a file named [path].properties. * * Then it will try to load a file named [path] (without extension). * * @param path the path of the file to load config from. * * @return a ConfigSupplier ready to load corresponding file. */ public ConfigSupplier fromFile(final String path) { return new ConfigSupplier() { @Override public RestxConfig get() { List<ConfigElement> elements = new ArrayList<>(); loadAllFromFile(elements, path); return StdRestxConfig.of(elements); } }; } /** * Gives the name of the system property that can be used to provide a file location * to load to override settings for a particular resource. * * By defaut the name is equal to the resource name where slashes `/` are replaced by dots `.`, * with `.location` suffix. * * Eg. * `myapp/settings` becomes `myapp.settings.location` * * @param resource the resource for which the system property name should be provided. * * @return the system property name. */ public String locationKeyForResource(String resource) { return resource.replace('/', '.') + ".location"; } protected void loadAllFromResource(List<ConfigElement> elements, String resource) { String locationKey = locationKeyForResource(resource); String location = System.getProperty(locationKey); if (location != null) { Path path = Paths.get(location).toAbsolutePath(); logger.info("loading {} settings from {}", resource, path); loadFileInto(path, elements); } else { logger.debug("system property `{}` is not set, no file to load to override settings from {}", locationKey, resource); } loadResourceInto(resource + "." + env + ".properties", elements); loadResourceInto(resource + ".properties", elements); } protected void loadAllFromFile(List<ConfigElement> elements, String path) { if (!path.endsWith(".properties")) { loadFileInto(Paths.get(path + "." + env + ".properties"), elements); loadFileInto(Paths.get(path + ".properties"), elements); } loadFileInto(Paths.get(path), elements); } protected void loadFileInto(Path path, List<ConfigElement> elements) { File file = path.toFile().getAbsoluteFile(); if (!file.exists()) { logger.debug("no settings loaded from {}: file not available", file); return; } if (!file.isFile()) { logger.warn("no settings loaded from {}: this is not a file", file); return; } try { Iterable<ConfigElement> loadedElements = StdRestxConfig.parse("file://" + file, asCharSource(file, Charsets.UTF_8)).elements(); Iterables.addAll(elements, loadedElements); logger.debug("loaded {} elements from {}", Iterables.size(loadedElements), file); } catch (IOException e) { logger.warn("can't load " + file + ": " + e.getMessage(), e); } } protected void loadResourceInto(String name, List<ConfigElement> elements) { URL r; r = Thread.currentThread().getContextClassLoader().getResource(name); if (r != null) { try { Iterable<ConfigElement> loadedElements = StdRestxConfig.parse("classpath:" + name, Resources.asCharSource(r, Charsets.UTF_8)).elements(); Iterables.addAll(elements, loadedElements); logger.debug("loaded {} elements from {}", Iterables.size(loadedElements), name); } catch (IOException e) { logger.warn("can't load " + name + ": " + e.getMessage(), e); } } else { logger.debug("no settings loaded from {}: resource not available", name); } } }