View Javadoc
1   package de.dlr.bt.stc.config;
2   
3   import java.io.IOException;
4   import java.nio.file.Files;
5   import java.nio.file.Path;
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Objects;
12  
13  import org.apache.commons.configuration2.HierarchicalConfiguration;
14  import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
15  import org.apache.commons.configuration2.builder.fluent.FileBasedBuilderParameters;
16  import org.apache.commons.configuration2.builder.fluent.Parameters;
17  import org.apache.commons.configuration2.ex.ConfigurationException;
18  import org.greenrobot.eventbus.EventBus;
19  
20  import de.dlr.bt.stc.eventbus.ConfigurationModifiedEvent;
21  import de.dlr.bt.stc.eventbus.ConfigurationModifiedEvent.ConfigurationModificationType;
22  import lombok.Getter;
23  import lombok.extern.slf4j.Slf4j;
24  
25  @Slf4j
26  public final class ConfigurationManager {
27  	private final List<Path> configurationPaths = new ArrayList<>();
28  
29  	private final Map<String, ACfg> configurations = new HashMap<>();
30  
31  	@Getter
32  	private final EventBus instanceEventBus;
33  	@Getter
34  	private final EventBus managementEventBus;
35  
36  	public ConfigurationManager(EventBus instanceEventBus, EventBus managementEventBus) {
37  		this.instanceEventBus = Objects.requireNonNull(instanceEventBus);
38  		this.managementEventBus = Objects.requireNonNull(managementEventBus);
39  	}
40  
41  	/**
42  	 * Add a {@link Path} (either designating a single configuration file or a
43  	 * directory containing .yml files) to the configuration list.
44  	 * 
45  	 * @param path A {@link Path} designating either a single file or a folder
46  	 *             containing configuration files. The folder is not read
47  	 *             recursively.
48  	 */
49  	public void addConfigurationPath(Path path) {
50  		configurationPaths.add(path);
51  	}
52  
53  	/**
54  	 * Load all configuration files previously added using
55  	 * {@link #addConfigurationPath(Path)}.
56  	 * 
57  	 */
58  	public void loadConfigurations() {
59  		final List<Path> configurationFiles = new ArrayList<>();
60  		findConfigurations(configurationPaths, configurationFiles);
61  		loadConfiguration(configurationFiles);
62  	}
63  
64  	private void findConfigurations(List<Path> paths, List<Path> results) {
65  		for (var p : paths) {
66  			findConfigurations(p, results);
67  		}
68  	}
69  
70  	private void findConfigurations(Path path, List<Path> results) {
71  		if (Files.isRegularFile(path)) {
72  			results.add(path);
73  		} else if (Files.isDirectory(path)) {
74  			try (var ds = Files.list(path)) {
75  				var files = ds.filter(ConfigurationManager::isYAMLFile).sorted().toList();
76  
77  				findConfigurations(files, results);
78  			} catch (IOException ioe) {
79  				log.error("Failed to list files in directory {}: {}", path, ioe);
80  			}
81  		} else {
82  			log.warn("Path supplied to ConfigurationManager which is neither regular nor directory: {}", path);
83  		}
84  	}
85  
86  	/**
87  	 * Check whether given path designates a YAML configuration file (regular file
88  	 * ending with .yml or .yaml)
89  	 * 
90  	 * @param path The path to check
91  	 * @return Whether given path is a configuration file
92  	 */
93  	private static boolean isYAMLFile(Path path) {
94  		if (!Files.isRegularFile(path))
95  			return false;
96  
97  		var lowerCase = path.getFileName().toString().toLowerCase();
98  
99  		return lowerCase.endsWith(".yml") || lowerCase.endsWith(".yaml");
100 	}
101 
102 	/**
103 	 * Load a single configuration file
104 	 * 
105 	 * @param parameters {@link FileBasedBuilderParameters} specifying the
106 	 *                   configuration file
107 	 * @throws ConfigurationException
108 	 */
109 	private void loadConfiguration(List<Path> configurationFiles) {
110 		List<HierarchicalConfiguration<?>> ymls = new ArrayList<>();
111 		for (var file : configurationFiles) {
112 
113 			FileBasedConfigurationBuilder<YAMLConfigurationSTC> configBuilder = new FileBasedConfigurationBuilder<>(
114 					YAMLConfigurationSTC.class);
115 
116 			configBuilder.configure(new Parameters().fileBased().setFile(file.toFile()));
117 
118 			try {
119 				ymls.add(configBuilder.getConfiguration());
120 			} catch (ConfigurationException ce) {
121 				log.error("Failed to parse configuration file {}: {}", file, ce);
122 			}
123 		}
124 
125 		var loadedConfigurations = ConfigHandler.separateAndResolveConfigs(ymls);
126 
127 		for (var entry : loadedConfigurations.entrySet()) {
128 			try {
129 				ACfg cfg = CfgFactory.getInstance().create(entry.getValue().getString("type"), entry.getValue());
130 				configurations.put(entry.getKey(), cfg);
131 				managementEventBus
132 						.post(new ConfigurationModifiedEvent(entry.getKey(), cfg, ConfigurationModificationType.ADD));
133 			} catch (ConfigurationException ce) {
134 				log.error("Failed to parse configuration item {}: {}", entry.getKey(), ce);
135 			}
136 		}
137 	}
138 
139 	/**
140 	 * Unload all configuration files, but keep information about the files/folders
141 	 * (to allow reloading configuration)
142 	 */
143 	public void unloadConfiguration() {
144 		for (var cfg : configurations.entrySet())
145 			managementEventBus.post(
146 					new ConfigurationModifiedEvent(cfg.getKey(), cfg.getValue(), ConfigurationModificationType.REMOVE));
147 
148 		configurations.clear();
149 	}
150 
151 	/**
152 	 * Clear all information about configuration files, including the files/folders.
153 	 */
154 	public void clearConfiguration() {
155 		unloadConfiguration();
156 
157 		configurationPaths.clear();
158 	}
159 
160 	public Map<String, ACfg> getConfigurations() {
161 		return Collections.unmodifiableMap(configurations);
162 	}
163 
164 }