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