package org.dcache.boot; import com.google.common.collect.Sets; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.LineNumberReader; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Objects; import java.util.Set; import org.dcache.util.ConfigurationProperties; import org.dcache.util.Version; import static java.util.stream.Collectors.joining; import static org.dcache.boot.Properties.*; public class LayoutBuilder { private final Set<File> _sourceFiles = Sets.newHashSet(); private final Set<File> _sourceDirectories = Sets.newHashSet(); private ConfigurationProperties.ProblemConsumer problemConsumer = new SilentProblemConsumer(); public LayoutBuilder setProblemConsumer(ConfigurationProperties.ProblemConsumer problemConsumer) { this.problemConsumer = problemConsumer; return this; } public Layout build() throws IOException, URISyntaxException { return loadLayout(loadConfiguration()); } private ConfigurationProperties loadSystemProperties() throws UnknownHostException { ConfigurationProperties config = new ConfigurationProperties(System.getProperties()); InetAddress localhost = InetAddress.getLocalHost(); config.setProperty(PROPERTY_HOST_NAME, localhost.getHostName().split("\\.")[0]); config.setProperty(PROPERTY_HOST_FQDN, localhost.getCanonicalHostName()); config.setProperty(PROPERTY_DCACHE_VERSION, Version.of(this).getVersion()); config.setProperty(PROPERTY_DCACHE_SCM_STATE, Version.of(this).getBuild()); return config; } private ConfigurationProperties loadConfigurationFromPath( ConfigurationProperties defaults, File path, ConfigurationProperties.UsageChecker usageChecker) throws IOException { ConfigurationProperties config = new ConfigurationProperties(defaults, usageChecker); if (path.isFile()) { _sourceFiles.add(path); config.loadFile(path); } else if (path.isDirectory()) { _sourceDirectories.add(path); File[] files = path.listFiles(); if (files != null) { Arrays.sort(files); for (File file: files) { if (file.isFile() && file.getName().endsWith(".properties")) { _sourceFiles.add(file); config.loadFile(file); } } } } else { /* To detect if path is created later, we consider the parent a source * directory. Touching that directory will cause the configuration cache * to be invalidated. */ _sourceDirectories.add(path.getParentFile()); } return config; } private ConfigurationProperties loadConfigurationByProperty( ConfigurationProperties defaults, String property, ConfigurationProperties.UsageChecker usageChecker) throws IOException { ConfigurationProperties config = defaults; String paths = config.getValue(property); if (paths != null) { for (String path: paths.split(PATH_DELIMITER)) { config = loadConfigurationFromPath(config, new File(path), usageChecker); } } return config; } /** * Loads plugins in a plugin directory. * * A plugin directory contains a number of plugins. Each plugin is * stored in a sub-directory containing that one plugin. */ private ConfigurationProperties loadPlugins( ConfigurationProperties defaults, File directory) throws IOException { ConfigurationProperties config = defaults; _sourceDirectories.add(directory); File[] files = directory.listFiles(); if (files != null) { for (File file: files) { if (file.isDirectory()) { config = loadConfigurationFromPath(config, file, new ConfigurationProperties.UniversalUsageChecker()); } } } return config; } private ConfigurationProperties loadConfiguration() throws UnknownHostException, IOException, URISyntaxException { /* Configuration properties are loaded from several * sources, starting with importing Java system * properties... */ ConfigurationProperties config = loadSystemProperties(); config.setProblemConsumer(problemConsumer); /* ... and a chain of properties files. */ config = loadConfigurationByProperty(config, PROPERTY_DEFAULTS_PATH, new ConfigurationProperties.UniversalUsageChecker()); for (String dir: getPluginDirs()) { config = loadPlugins(config, new File(dir)); } config = loadConfigurationByProperty(config, PROPERTY_SETUP_PATH, new DcacheConfigurationUsageChecker()); return config; } private Layout loadLayout(ConfigurationProperties config) throws IOException, URISyntaxException { String path = config.getValue(PROPERTY_DCACHE_LAYOUT_URI); if (path == null) { throw new IOException("Undefined property: " + PROPERTY_DCACHE_LAYOUT_URI); } URI uri = new URI(path); Layout layout = new Layout(config); if (!path.isEmpty()) { try { layout.load(uri); } catch (FileNotFoundException e) { config.getProblemConsumer().warning(e.getMessage()); } if (Objects.equals(uri.getScheme(), "file")) { _sourceFiles.add(new File(uri.getPath())); } else { layout.properties().setProperty(PROPERTY_DCACHE_CONFIG_CACHE, "false"); } } layout.properties().setProperty(PROPERTY_DCACHE_CONFIG_FILES, joinPaths(_sourceFiles)); layout.properties().setProperty(PROPERTY_DCACHE_CONFIG_DIRS, joinPaths(_sourceDirectories)); return layout; } private static String joinPaths(Set<File> sourceFiles) { return sourceFiles.stream().map(f -> '"' + f.getPath() + '"').collect(joining(" ")); } /** * Returns the top-level plugin directory. * * To allow the plugin directory to be configurable, we first have * to load all the configuration files without the plugins. */ private String[] getPluginDirs() throws IOException, URISyntaxException { ConfigurationProperties config = loadSystemProperties(); config.setProblemConsumer(new SilentProblemConsumer()); config = loadConfigurationByProperty(config, PROPERTY_DEFAULTS_PATH, new ConfigurationProperties.UniversalUsageChecker()); config = loadConfigurationByProperty(config, PROPERTY_SETUP_PATH, new DcacheConfigurationUsageChecker()); config = loadLayout(config).properties(); String dir = config.getValue(PROPERTY_PLUGIN_PATH); return (dir == null) ? new String[0] : dir.split(PATH_DELIMITER); } private static class SilentProblemConsumer implements ConfigurationProperties.ProblemConsumer { @Override public void setFilename(String name) { } @Override public void setLineNumberReader(LineNumberReader reader) { } @Override public void error(String message) { } @Override public void warning(String message) { } @Override public void info(String message) { } } }