1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration2.builder.combined;
18
19 import static org.apache.commons.configuration2.TempDirUtils.newFile;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertTrue;
22
23 import java.io.File;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.io.PrintWriter;
27 import java.text.MessageFormat;
28
29 import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
30 import org.apache.commons.configuration2.CombinedConfiguration;
31 import org.apache.commons.configuration2.Configuration;
32 import org.apache.commons.configuration2.XMLConfiguration;
33 import org.apache.commons.configuration2.builder.BasicBuilderParameters;
34 import org.apache.commons.configuration2.builder.BasicBuilderProperties;
35 import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
36 import org.apache.commons.configuration2.builder.CopyObjectDefaultHandler;
37 import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
38 import org.apache.commons.configuration2.builder.FileBasedBuilderProperties;
39 import org.apache.commons.configuration2.builder.ReloadingDetectorFactory;
40 import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
41 import org.apache.commons.configuration2.builder.fluent.Parameters;
42 import org.apache.commons.configuration2.ex.ConfigurationException;
43 import org.apache.commons.configuration2.io.FileHandler;
44 import org.apache.commons.configuration2.reloading.AlwaysReloadingDetector;
45 import org.apache.commons.configuration2.reloading.RandomReloadingDetector;
46 import org.apache.commons.configuration2.sync.ReadWriteSynchronizer;
47 import org.apache.commons.configuration2.sync.Synchronizer;
48 import org.apache.commons.configuration2.tree.MergeCombiner;
49 import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
50 import org.junit.jupiter.api.BeforeEach;
51 import org.junit.jupiter.api.Test;
52 import org.junit.jupiter.api.io.TempDir;
53
54
55
56
57 public class TestReloadingCombinedConfigurationBuilderFileBased {
58
59
60
61
62 private static final class ConstantConfigurationBuilder extends BasicConfigurationBuilder<BaseHierarchicalConfiguration> {
63 private final BaseHierarchicalConfiguration configuration;
64
65 public ConstantConfigurationBuilder(final BaseHierarchicalConfiguration conf) {
66 super(BaseHierarchicalConfiguration.class);
67 configuration = conf;
68 }
69
70 @Override
71 public BaseHierarchicalConfiguration getConfiguration() throws ConfigurationException {
72 return configuration;
73 }
74 }
75
76
77
78
79 private static final class ReloadThread extends Thread {
80
81
82 private final ReloadingCombinedConfigurationBuilder builder;
83
84
85 private final int[] failures;
86
87
88 private final int index;
89
90
91 private final int count;
92
93 ReloadThread(final ReloadingCombinedConfigurationBuilder bldr, final int[] failures, final int index, final int count) {
94 builder = bldr;
95 this.failures = failures;
96 this.index = index;
97 this.count = count;
98 }
99
100 @Override
101 public void run() {
102 failures[index] = 0;
103 for (int i = 0; i < count; i++) {
104 try {
105 builder.getReloadingController().checkForReloading(null);
106 final String value = builder.getConfiguration().getString("/property[@name='config']/@value");
107 if (value == null || !value.equals("100")) {
108 ++failures[index];
109 }
110 } catch (final Exception ex) {
111 ++failures[index];
112 }
113 }
114 }
115 }
116
117
118 private static final String PROP_SRC = "override.xml";
119
120
121 private static final String PROP_RELOAD = "default.xmlReload";
122
123
124 private static final String RELOAD_CONTENT = "<config><default><xmlReload{1}>{0}</xmlReload{1}></default></config>";
125
126
127
128
129
130
131
132 private static void addReloadSource(final Configuration config, final String fileName) {
133 config.addProperty(PROP_SRC + "(-1)[@fileName]", fileName);
134 config.addProperty(PROP_SRC + "[@config-reload]", Boolean.TRUE);
135 }
136
137
138
139
140
141
142
143 private static String testProperty(final int idx) {
144 return PROP_RELOAD + idx;
145 }
146
147
148
149
150
151
152
153
154 private static void writeFile(final File file, final String content) throws IOException {
155 try (PrintWriter out = new PrintWriter(new FileWriter(file))) {
156 out.print(content);
157 }
158 }
159
160
161 @TempDir
162 public File tempFolder;
163
164
165 private Parameters parameters;
166
167
168 private ReloadingCombinedConfigurationBuilder builder;
169
170
171
172
173
174
175
176
177
178
179 private void checkReloadDefinitionFile(final File defFile) throws IOException, ConfigurationException, InterruptedException {
180 final File src1 = writeReloadFile(null, 1, 0);
181 final File src2 = writeReloadFile(null, 1, 1);
182 writeDefinitionFile(defFile, src1);
183 CombinedConfiguration config = builder.getConfiguration();
184 assertEquals(0, config.getInt(testProperty(1)));
185
186
187 boolean reloaded = false;
188 for (int attempts = 0; attempts < 50 && !reloaded; attempts++) {
189 writeDefinitionFile(defFile, src2);
190 reloaded = builder.getReloadingController().checkForReloading(null);
191 if (!reloaded) {
192 Thread.sleep(100);
193 }
194 }
195 assertTrue(reloaded);
196 config = builder.getConfiguration();
197 assertEquals(1, config.getInt(testProperty(1)));
198 }
199
200 @BeforeEach
201 public void setUp() throws Exception {
202 parameters = new Parameters();
203 builder = new ReloadingCombinedConfigurationBuilder();
204 }
205
206
207
208
209 @Test
210 void testConcurrentGetAndReload() throws Exception {
211 final int threadCount = 4;
212 final int loopCount = 100;
213 final ReloadingDetectorFactory detectorFactory = (handler, params) -> new RandomReloadingDetector();
214 final BaseHierarchicalConfiguration defConf = new BaseHierarchicalConfiguration();
215 defConf.addProperty("header.result.nodeCombiner[@config-class]", MergeCombiner.class.getName());
216 defConf.addProperty("header.result.expressionEngine[@config-class]", XPathExpressionEngine.class.getName());
217 addReloadSource(defConf, "configA.xml");
218 addReloadSource(defConf, "configB.xml");
219 final Synchronizer sync = new ReadWriteSynchronizer();
220 builder.configure(parameters.combined().setDefinitionBuilder(new ConstantConfigurationBuilder(defConf)).setSynchronizer(sync)
221 .registerChildDefaultsHandler(BasicBuilderProperties.class, new CopyObjectDefaultHandler(new BasicBuilderParameters().setSynchronizer(sync)))
222 .registerChildDefaultsHandler(FileBasedBuilderProperties.class,
223 new CopyObjectDefaultHandler(new FileBasedBuilderParametersImpl().setReloadingDetectorFactory(detectorFactory))));
224
225 assertEquals("100", builder.getConfiguration().getString("/property[@name='config']/@value"));
226
227 final Thread[] testThreads = new Thread[threadCount];
228 final int[] failures = new int[threadCount];
229
230 for (int i = 0; i < testThreads.length; ++i) {
231 testThreads[i] = new ReloadThread(builder, failures, i, loopCount);
232 testThreads[i].start();
233 }
234
235 int totalFailures = 0;
236 for (int i = 0; i < testThreads.length; ++i) {
237 testThreads[i].join();
238 totalFailures += failures[i];
239 }
240 assertEquals(0, totalFailures);
241 }
242
243
244
245
246 @Test
247 void testReloadDefinitionFileDefaultBuilder() throws ConfigurationException, IOException, InterruptedException {
248 final File defFile = newFile(tempFolder);
249 builder.configure(parameters.combined().setDefinitionBuilderParameters(parameters.xml().setReloadingRefreshDelay(0L).setFile(defFile)));
250 checkReloadDefinitionFile(defFile);
251 }
252
253
254
255
256
257 @Test
258 void testReloadDefinitionFileExplicitBuilder() throws ConfigurationException, IOException, InterruptedException {
259 final File defFile = newFile(tempFolder);
260 builder.configure(parameters.combined().setDefinitionBuilder(
261 new ReloadingFileBasedConfigurationBuilder<>(XMLConfiguration.class).configure(parameters.xml().setReloadingRefreshDelay(0L).setFile(defFile))));
262 checkReloadDefinitionFile(defFile);
263 }
264
265
266
267
268 @Test
269 void testReloadFromFile() throws ConfigurationException, IOException {
270 final File xmlConf1 = writeReloadFile(null, 1, 0);
271 final File xmlConf2 = writeReloadFile(null, 2, 0);
272 final ReloadingDetectorFactory detectorFactory = (handler, params) -> new AlwaysReloadingDetector();
273 final BaseHierarchicalConfiguration defConf = new BaseHierarchicalConfiguration();
274 addReloadSource(defConf, xmlConf1.getAbsolutePath());
275 addReloadSource(defConf, xmlConf2.getAbsolutePath());
276 builder.configure(parameters.combined().setDefinitionBuilder(new ConstantConfigurationBuilder(defConf)).registerChildDefaultsHandler(
277 FileBasedBuilderProperties.class, new CopyObjectDefaultHandler(new FileBasedBuilderParametersImpl().setReloadingDetectorFactory(detectorFactory))));
278 CombinedConfiguration config = builder.getConfiguration();
279 assertEquals(0, config.getInt(testProperty(1)));
280 assertEquals(0, config.getInt(testProperty(2)));
281
282 writeReloadFile(xmlConf1, 1, 1);
283 builder.getReloadingController().checkForReloading(null);
284 config = builder.getConfiguration();
285 assertEquals(1, config.getInt(testProperty(1)));
286 assertEquals(0, config.getInt(testProperty(2)));
287
288 writeReloadFile(xmlConf2, 2, 2);
289 builder.getReloadingController().checkForReloading(null);
290 config = builder.getConfiguration();
291 assertEquals(1, config.getInt(testProperty(1)));
292 assertEquals(2, config.getInt(testProperty(2)));
293 }
294
295
296
297
298
299
300
301
302 private void writeDefinitionFile(final File defFile, final File src) throws ConfigurationException {
303 final XMLConfiguration defConf = new XMLConfiguration();
304 addReloadSource(defConf, src.getAbsolutePath());
305 new FileHandler(defConf).save(defFile);
306 }
307
308
309
310
311
312
313
314
315
316
317 private File writeReloadFile(final File f, final int tagIdx, final int value) throws IOException {
318 return writeReloadFile(f, MessageFormat.format(RELOAD_CONTENT, value, tagIdx));
319 }
320
321
322
323
324
325
326
327
328
329
330 private File writeReloadFile(final File f, final String content) throws IOException {
331 final File file = f != null ? f : newFile(tempFolder);
332 writeFile(file, content);
333 return file;
334 }
335 }