1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration2;
19
20 import static org.apache.commons.configuration2.TempDirUtils.newFile;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertSame;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27
28 import java.io.File;
29 import java.io.IOException;
30 import java.nio.file.StandardCopyOption;
31 import java.util.Random;
32
33 import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
34 import org.apache.commons.configuration2.builder.BuilderConfigurationWrapperFactory;
35 import org.apache.commons.configuration2.builder.ConfigurationBuilder;
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.combined.CombinedConfigurationBuilder;
40 import org.apache.commons.configuration2.builder.combined.MultiFileConfigurationBuilder;
41 import org.apache.commons.configuration2.builder.combined.ReloadingCombinedConfigurationBuilder;
42 import org.apache.commons.configuration2.builder.fluent.Parameters;
43 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
44 import org.apache.commons.configuration2.ex.ConfigurationException;
45 import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
46 import org.apache.commons.configuration2.interpol.Lookup;
47 import org.apache.commons.configuration2.io.FileHandler;
48 import org.apache.commons.configuration2.sync.LockMode;
49 import org.apache.commons.configuration2.sync.ReadWriteSynchronizer;
50 import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
51 import org.apache.commons.io.FileUtils;
52 import org.junit.jupiter.api.BeforeAll;
53 import org.junit.jupiter.api.Test;
54 import org.junit.jupiter.api.io.TempDir;
55
56 public class TestDynamicCombinedConfiguration {
57 private final class ReaderThread extends Thread {
58 private volatile boolean running = true;
59 private volatile boolean failed;
60 private final CombinedConfigurationBuilder builder;
61 private final Random random;
62
63 public ReaderThread(final CombinedConfigurationBuilder b) {
64 builder = b;
65 random = new Random();
66 }
67
68 public boolean failed() {
69 return failed;
70 }
71
72 @Override
73 public void run() {
74 try {
75 while (running) {
76 final CombinedConfiguration combined = builder.getConfiguration();
77 final String bcId = combined.getString("Product/FIIndex/FI[@id='123456781']");
78 if ("ID0001".equalsIgnoreCase(bcId)) {
79 if (failed) {
80 System.out.println("Thread failed, but recovered");
81 }
82 failed = false;
83 } else {
84 failed = true;
85 }
86 final int sleepTime = random.nextInt(75);
87 Thread.sleep(sleepTime);
88 }
89 } catch (final ConfigurationException cex) {
90 failed = true;
91 } catch (final InterruptedException iex) {
92 Thread.currentThread().interrupt();
93 }
94 }
95
96 public void shutdown() {
97 running = false;
98 interrupt();
99 }
100
101 }
102 private static final class ReloadThread extends Thread {
103 private final CombinedConfigurationBuilder builder;
104 private final int[] failures;
105 private final int index;
106 private final int count;
107 private final String expected;
108 private final String id;
109 private final boolean useId;
110 private final Random random;
111
112 ReloadThread(final CombinedConfigurationBuilder b, final int[] failures, final int index, final int count, final boolean useId, final String id,
113 final String expected) {
114 builder = b;
115 this.failures = failures;
116 this.index = index;
117 this.count = count;
118 this.expected = expected;
119 this.id = id;
120 this.useId = useId;
121 random = new Random();
122 }
123
124 @Override
125 public void run() {
126 failures[index] = 0;
127
128 if (useId) {
129 ThreadLookup.setId(id);
130 }
131 for (int i = 0; i < count; i++) {
132 try {
133 if (random.nextBoolean()) {
134
135 builder.resetResult();
136 }
137 final CombinedConfiguration combined = builder.getConfiguration();
138 final String value = combined.getString("rowsPerPage", null);
139 if (value == null || !value.equals(expected)) {
140 ++failures[index];
141 }
142 } catch (final Exception ex) {
143 ++failures[index];
144 }
145 }
146 }
147 }
148
149 public static class ThreadLookup implements Lookup {
150
151 private static final ThreadLocal<String> ID = new ThreadLocal<>();
152
153 public static void setId(final String value) {
154 ID.set(value);
155 }
156
157 public ThreadLookup() {
158 }
159
160 @Override
161 public String lookup(final String key) {
162 if (key == null || !key.equals("Id")) {
163 return null;
164 }
165 final String value = System.getProperty("Id");
166 if (value != null) {
167 return value;
168 }
169 return ID.get();
170
171 }
172 }
173
174 private static final String PATTERN = "${sys:Id}";
175 private static final String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
176
177 private static final String DEFAULT_FILE = "target/test-classes/testMultiConfiguration_default.xml";
178
179 private static final File MULTI_TENENT_FILE = ConfigurationAssert.getTestFile("testMultiTenentConfigurationBuilder4.xml");
180
181 private static final File MULTI_DYNAMIC_FILE = ConfigurationAssert.getTestFile("testMultiTenentConfigurationBuilder5.xml");
182
183
184 private static final int THREAD_COUNT = 3;
185
186
187 private static final int LOOP_COUNT = 100;
188
189
190 private static Parameters parameters;
191
192 @BeforeAll
193 public static void setUpOnce() {
194 parameters = new Parameters();
195 }
196
197
198 @TempDir
199 public File tempFolder;
200
201 private void copyFile(final File input, final File output) throws IOException {
202 FileUtils.copyFile(input, output, StandardCopyOption.REPLACE_EXISTING);
203
204 output.setLastModified(System.currentTimeMillis());
205 }
206
207
208
209
210
211
212
213
214 private SynchronizerTestImpl prepareSynchronizerTest(final Configuration config) {
215 final SynchronizerTestImpl sync = new SynchronizerTestImpl();
216 config.setSynchronizer(sync);
217 config.lock(LockMode.READ);
218 config.unlock(LockMode.READ);
219 sync.clear();
220 return sync;
221 }
222
223
224
225
226 @Test
227 public void testAddConfigurationSynchronized() {
228 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
229 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
230 config.addConfiguration(new PropertiesConfiguration());
231 sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
232 }
233
234 @Test
235 public void testConcurrentGetAndReload() throws Exception {
236 System.getProperties().remove("Id");
237 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
238 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
239 final CombinedConfiguration config = builder.getConfiguration();
240
241 assertEquals("50", config.getString("rowsPerPage"));
242 final Thread[] testThreads = new Thread[THREAD_COUNT];
243 final int[] failures = new int[THREAD_COUNT];
244
245 for (int i = 0; i < testThreads.length; ++i) {
246 testThreads[i] = new ReloadThread(builder, failures, i, LOOP_COUNT, false, null, "50");
247 testThreads[i].start();
248 }
249
250 int totalFailures = 0;
251 for (int i = 0; i < testThreads.length; ++i) {
252 testThreads[i].join();
253 totalFailures += failures[i];
254 }
255 assertEquals(0, totalFailures);
256 }
257
258 @Test
259 public void testConcurrentGetAndReload2() throws Exception {
260 System.getProperties().remove("Id");
261 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
262 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
263 final CombinedConfiguration config = builder.getConfiguration();
264
265 assertEquals("50", config.getString("rowsPerPage"));
266
267 final Thread[] testThreads = new Thread[THREAD_COUNT];
268 final int[] failures = new int[THREAD_COUNT];
269 System.setProperty("Id", "2002");
270 assertEquals("25", config.getString("rowsPerPage"));
271 for (int i = 0; i < testThreads.length; ++i) {
272 testThreads[i] = new ReloadThread(builder, failures, i, LOOP_COUNT, false, null, "25");
273 testThreads[i].start();
274 }
275
276 int totalFailures = 0;
277 for (int i = 0; i < testThreads.length; ++i) {
278 testThreads[i].join();
279 totalFailures += failures[i];
280 }
281 System.getProperties().remove("Id");
282 assertEquals(0, totalFailures);
283 }
284
285 @Test
286 public void testConcurrentGetAndReloadFile() throws Exception {
287 final int threadCount = 25;
288 System.getProperties().remove("Id");
289 System.setProperty("TemporaryFolder", tempFolder.getAbsolutePath());
290
291 File input = new File("target/test-classes/testMultiDynamic_default.xml");
292 final File output = newFile("testMultiDynamic_default.xml", tempFolder);
293 output.delete();
294 output.getParentFile().mkdir();
295 copyFile(input, output);
296
297 final ReloadingCombinedConfigurationBuilder builder = new ReloadingCombinedConfigurationBuilder();
298 builder.configure(parameters.combined().setSynchronizer(new ReadWriteSynchronizer())
299 .setDefinitionBuilderParameters(new FileBasedBuilderParametersImpl().setFile(MULTI_DYNAMIC_FILE)).registerChildDefaultsHandler(
300 FileBasedBuilderProperties.class, new CopyObjectDefaultHandler(new FileBasedBuilderParametersImpl().setReloadingRefreshDelay(1L))));
301 CombinedConfiguration config = builder.getConfiguration();
302 assertEquals("ID0001", config.getString("Product/FIIndex/FI[@id='123456781']"));
303
304 final ReaderThread[] testThreads = new ReaderThread[threadCount];
305 for (int i = 0; i < testThreads.length; ++i) {
306 testThreads[i] = new ReaderThread(builder);
307 testThreads[i].start();
308 }
309
310 builder.getReloadingController().checkForReloading(null);
311 Thread.sleep(2000);
312
313 input = new File("target/test-classes/testMultiDynamic_default2.xml");
314 copyFile(input, output);
315
316 Thread.sleep(2000);
317 assertTrue(builder.getReloadingController().checkForReloading(null));
318 config = builder.getConfiguration();
319 final String id = config.getString("Product/FIIndex/FI[@id='123456782']");
320 assertNotNull(id);
321 final String rows = config.getString("rowsPerPage");
322 assertEquals("25", rows);
323
324 for (final ReaderThread testThread : testThreads) {
325 testThread.shutdown();
326 testThread.join();
327 }
328 for (final ReaderThread testThread : testThreads) {
329 assertFalse(testThread.failed());
330 }
331 assertEquals("ID0002", config.getString("Product/FIIndex/FI[@id='123456782']"));
332 output.delete();
333 }
334
335 @Test
336 public void testConcurrentGetAndReloadMultipleClients() throws Exception {
337 System.getProperties().remove("Id");
338 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
339 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
340 final CombinedConfiguration config = builder.getConfiguration();
341
342 assertEquals("50", config.getString("rowsPerPage"));
343
344 final Thread[] testThreads = new Thread[THREAD_COUNT];
345 final int[] failures = new int[THREAD_COUNT];
346 final String[] ids = {null, "2002", "3001", "3002", "3003"};
347 final String[] expected = {"50", "25", "15", "25", "50"};
348 for (int i = 0; i < testThreads.length; ++i) {
349 testThreads[i] = new ReloadThread(builder, failures, i, LOOP_COUNT, true, ids[i], expected[i]);
350 testThreads[i].start();
351 }
352
353 int totalFailures = 0;
354 for (int i = 0; i < testThreads.length; ++i) {
355 testThreads[i].join();
356 totalFailures += failures[i];
357 }
358 System.getProperties().remove("Id");
359 if (totalFailures != 0) {
360 System.out.println("Failures:");
361 for (int i = 0; i < testThreads.length; ++i) {
362 System.out.println("Thread " + i + " " + failures[i]);
363 }
364 }
365 assertEquals(0, totalFailures);
366 }
367
368 @Test
369 public void testConfiguration() throws Exception {
370 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
371 final DefaultListDelimiterHandler listHandler = new DefaultListDelimiterHandler(',');
372 config.setListDelimiterHandler(listHandler);
373 final XPathExpressionEngine engine = new XPathExpressionEngine();
374 config.setExpressionEngine(engine);
375 config.setKeyPattern(PATTERN);
376 final ConfigurationBuilder<XMLConfiguration> multiBuilder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class)
377 .configure(parameters.multiFile().setFilePattern(PATTERN1).setPrefixLookups(ConfigurationInterpolator.getDefaultPrefixLookups())
378 .setManagedBuilderParameters(parameters.xml().setExpressionEngine(engine).setListDelimiterHandler(listHandler)));
379 final BuilderConfigurationWrapperFactory wrapFactory = new BuilderConfigurationWrapperFactory();
380 config.addConfiguration(wrapFactory.createBuilderConfigurationWrapper(HierarchicalConfiguration.class, multiBuilder), "Multi");
381 final XMLConfiguration xml = new XMLConfiguration();
382 xml.setExpressionEngine(engine);
383 final FileHandler handler = new FileHandler(xml);
384 handler.setFile(new File(DEFAULT_FILE));
385 handler.load();
386 config.addConfiguration(xml, "Default");
387
388 verify("1001", config, 15);
389 verify("1002", config, 25);
390 verify("1003", config, 35);
391 verify("1004", config, 50);
392 assertEquals("a,b,c", config.getString("split/list3/@values"));
393 assertEquals(0, config.getMaxIndex("split/list3/@values"));
394 assertEquals("a\\,b\\,c", config.getString("split/list4/@values"));
395 assertEquals("OK-1", config.getString("buttons/name"));
396 assertEquals(3, config.getMaxIndex("buttons/name"));
397 assertEquals("a\\,b\\,c", config.getString("split/list2"));
398 assertEquals(18, config.size());
399 config.addProperty("listDelimiterTest", "1,2,3");
400 assertEquals("1", config.getString("listDelimiterTest"));
401 }
402
403
404
405
406 @Test
407 public void testGetConfigurationByIdxSynchronized() {
408 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
409 final Configuration child = new PropertiesConfiguration();
410 config.addConfiguration(child);
411 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
412 assertSame(child, config.getConfiguration(0));
413 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
414 }
415
416
417
418
419 @Test
420 public void testGetConfigurationByNameSynchronized() {
421 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
422 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
423 assertNull(config.getConfiguration("unknown config"));
424 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
425 }
426
427
428
429
430 @Test
431 public void testGetConfigurationNamesSynchronized() {
432 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
433 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
434 config.getConfigurationNames();
435 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
436 }
437
438
439
440
441 @Test
442 public void testGetNumberOfConfigurationsSynchronized() {
443 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
444 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
445 config.getNumberOfConfigurations();
446 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
447 }
448
449
450
451
452 @Test
453 public void testRemoveConfigurationSynchronized() {
454 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
455 final String configName = "testConfig";
456 config.addConfiguration(new PropertiesConfiguration(), configName);
457 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
458 config.removeConfiguration(configName);
459 sync.verifyContains(Methods.BEGIN_WRITE);
460 }
461
462
463
464
465 @Test
466 public void testUpdateConfiguration() throws ConfigurationException {
467 System.getProperties().remove("Id");
468 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
469 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
470 final CombinedConfiguration config = builder.getConfiguration();
471 config.getConfiguration(1).setProperty("rowsPerPage", "25");
472 assertEquals("25", config.getString("rowsPerPage"));
473 }
474
475 private void verify(final String key, final DynamicCombinedConfiguration config, final int rows) {
476 System.setProperty("Id", key);
477 assertEquals(config.getInt("rowsPerPage"), rows);
478 }
479 }